user2386226
user2386226

Reputation:

How do I avoid memory leaks from array list event listeners?

I am getting a memory leak notification via leak canary where it says my fragment instance leaks due to references held from eventlisteners and Arraylist.array. Not sure how to fix this, any ideas?

@Override
ArrayList<myInterface> getnewList() {
    ArrayList<myInterface> inst = new ArrayList<>();
    inst.addAll(myRepository.getList());
    inst.addAll(myRepository.getOtherList());
    Collections.sort(inst, myRepository.myComparator);
    return inst;
}

Here's the leak trace which gives an indication of the leak:

In com.myproject.project2.alpha.debug:3.0.0:3000000.
* com.project.newzy.dashboard.myListFragment has leaked:
* GC ROOT static com.myproject.repository.myRepository.eventListeners
* references java.util.ArrayList.array
* references array java.lang.Object[].[0]
* leaks com.project.newzy.dashboard.myListFragment instance

* Retaining: 251 KB.
* Reference Key: cc806908-52f6-42f5-be98-b39665dfa218
* Device: samsung samsung SM-J327P j3popltespr
* Android Version: 6.0.1 API: 23 LeakCanary: 1.5.1 1be44b3
* Durations: watch=5463ms, gc=131ms, heap dump=3776ms, analysis=40370ms

* Details:
* Class com.myproject.repository.myRepository
|   static eventListeners = java.util.ArrayList@587750272 (0x23085b80)
|   static Comparator = com.myproject.repository.myRepository$5@587741136 (0x230837d0)
|   static $staticOverhead = byte[40]@584327169 (0x22d42001)
|   static initialized = true
|   static lock = java.lang.Object@587741152 (0x230837e0)
|   static cache = java.util.concurrent.ConcurrentHashMap@587732480 (0x23081600)
* Instance of java.util.ArrayList
|   static $staticOverhead = byte[16]@1893860329 (0x70e203e9)
|   static MIN_CAPACITY_INCREMENT = 12
|   static serialVersionUID = 8683452581122892189
|   array = java.lang.Object[12]@591375616 (0x233fad00)
|   size = 1
|   modCount = 1
|   shadow$_klass_ = java.util.ArrayList
|   shadow$_monitor_ = 0
* Array of java.lang.Object[]
|   [0] = com.project.newzy.dashboard.myListFragment@596231392 (0x2389c4e0)
|   [1] = null
|   [2] = null
|   [3] = null
|   [4] = null
|   [5] = null
|   [6] = null
|   [7] = null
|   [8] = null
|   [9] = null
|   [10] = null
|   [11] = null
* Instance of com.project.newzy.dashboard.myListFragment
|   static $staticOverhead = byte[16]@583464961 (0x22c6f801)
|   static serialVersionUID = 0
|   static $change = null
|   adapter = com.project.newzy.dashboard.DashboardAdapter@590877408 (0x233812e0)
|   myRepository = com.myproject.repository.myRepository@587661072 (0x2306ff10)
|   inst = java.util.ArrayList@591400160 (0x23400ce0)
|   emptyLayout = android.widget.RelativeLayout@593542144 (0x2360bc00)
|   emptyMessage = android.support.v7.widget.AppCompatTextView@593544192 (0x2360c400)
|   floatingActionButton = android.support.design.widget.FloatingActionButton@594071552 (0x2368d000)
|   roomList = com.project.gui.advancedrecyclerview.AdvancedRecyclerView@593541120 (0x2360b800)
|   selectedVGroupID = null
|   listAdapter = com.project.newzy.dashboard.DashboardAdapter@590877408 (0x233812e0)
|   listDivider = com.project.newzy.base.helpers.DividerItemDecoration@589723120 (0x232675f0)
|   listManager = android.support.v7.widget.LinearLayoutManager@591052128 (0x233abd60)
|   listView = com.project.gui.advancedrecyclerview.AdvancedRecyclerView@593541120 (0x2360b800)
|   mAdded = true
|   mAnimationInfo = null
|   mArguments = null
|   mBackStackNesting = 0
|   mCalled = true
|   mCheckedForLoaderManager = true
|   mChildFragmentManager = android.support.v4.app.FragmentManagerImpl@588818688 (0x2318a900)
|   mChildNonConfig = null
|   mContainer = android.support.v4.view.ViewPager@597927936 (0x23a3a800)
|   mContainerId = 2131755178
|   mDeferStart = false
|   mDetached = false
|   mFragmentId = 2131755178
|   mFragmentManager = android.support.v4.app.FragmentManagerImpl@598589728 (0x23adc120)
|   mFromLayout = false
|   mHasMenu = false
|   mHidden = false
|   mHiddenChanged = false
|   mHost = android.support.v4.app.FragmentActivity$HostCallbacks@598610224 (0x23ae1130)
|   mInLayout = false
|   mIndex = 1
|   mInnerView = android.widget.RelativeLayout@593536000 (0x2360a400)
|   mIsNewlyAdded = false
|   mLoaderManager = null
|   mLoadersStarted = true
|   mMenuVisible = true
|   mParentFragment = null
|   mPostponedAlpha = 0.0
|   mRemoving = false
|   mRestored = false
|   mRetainInstance = false
|   mRetaining = false
|   mSavedFragmentState = null
|   mSavedViewState = null
|   mState = 5
|   mTag = java.lang.String@590080848 (0x232beb50)
|   mTarget = null
|   mTargetIndex = -1
|   mTargetRequestCode = 0
|   mUserVisibleHint = true
|   mView = android.widget.RelativeLayout@593536000 (0x2360a400)
|   mWho = java.lang.String@591143744 (0x233c2340)
|   shadow$_klass_ = com.project.newzy.dashboard.myListFragment
|   shadow$_monitor_ = -2032154546
* Excluded Refs:
| Field: android.view.inputmethod.InputMethodManager.mNextServedView
| Field: android.view.inputmethod.InputMethodManager.mServedView
| Field: android.view.inputmethod.InputMethodManager.mServedInputConnection
| Field: android.view.inputmethod.InputMethodManager.mCurRootView
| Field: android.os.UserManager.mContext
| Field: android.net.ConnectivityManager.sInstance
| Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
| Thread:FinalizerWatchdogDaemon (always)
| Thread:main (always)
| Thread:LeakCanary-Heap-Dump (always)
| Class:java.lang.ref.WeakReference (always)
| Class:java.lang.ref.SoftReference (always)
| Class:java.lang.ref.PhantomReference (always)
| Class:java.lang.ref.Finalizer (always)
| Class:java.lang.ref.FinalizerReference (always)

Please let me know if you guys have faced this before and have any clues about how to go about fixing it?

Upvotes: 2

Views: 1620

Answers (2)

wdina
wdina

Reputation: 455

It seems that your myRepository class holds a reference to a Fragment instance myListFragment.

I do not know the implementation of myRepository but if I can make a guess, this class is probably a Singleton class so it resides in memory for the whole application process. Fragment and Activity context is a big memory chunk and since a Singleton class holds a reference to that memory, Garbage Collector cannot clear that Fragment/Activity memory when it finishes its lifecycle which means your Fragment instance will also reside in memory throughout the whole application lifecycle. One blog about meomory leak: https://android.jlelse.eu/memory-leak-patterns-in-android-4741a7fcb570

I can recommend one fix to your problem:

  1. Make your data manager class dumb and stateless. Do not attach a listener inside your data manager class. Instead, make API to make CRUD operations to data and only attach listeners in Fragment class. When an event is triggered, call data manager's methods accordingly. This makes your data manager not mix logic with any specific Fragment class which you do not need to change anything should you need to remove Fragments later in the project.

Hope this helps.

Upvotes: 0

dan
dan

Reputation: 13272

The stack trace shows that the com.myproject.repository.myRepository is holding a reference to com.project.newzy.dashboard.myListFragment in the eventListeners array.

I'm not sure exactly what your myRepository is but (probably it used as an Observable) it's holding a reference to an myListFragment (probably a Fragment) that the UI needs to destroy.

To solve the issue you need to ensure that when the myListFragment is about to be destroyed it's no longer part of the eventListeners array. Just remove the listener from the array in onPause and register it back in onResume.

Fragment Lifecycle

Upvotes: 1

Related Questions