java - Why does this Observable.timer() cause a memory leak? -


leakcanary reports leak articleactivity via rxcomputationthreadpool-1. so, identified articlecontainerfragment.starttimer() method 1 that's causing it. after removing creation of observable.timer() call, no more memory leak reported. still need use timer though, can me identify why leak occurring? unsubscribing in right places believe - not sure why getting leak in first place.

public class articlecontainerfragment extends basefragment<articlecontainercomponent, articlecontainerpresenter> implements articlecontainerview {     @bind(r.id.article_viewpager)     viewpager viewpager;      @inject     articlecontainerpresenter presenter;      articleadapter adapter;      @icicle     @nullable     genericarticlecategory genericarticlecategory;     @icicle     articlestyle articlestyle;      subscription subscription;      private toolbar toolbar;      @nullable     private integer initialarticleposition;      public articlecontainerfragment() {     }      public static articlecontainerfragment newinstance(articlestyle articlestyle, genericarticlecategory genericarticlecategory) {         articlecontainerfragment newfrag = new articlecontainerfragment();         newfrag.articlestyle = articlestyle;         newfrag.genericarticlecategory = genericarticlecategory;         return newfrag;     }      public static articlecontainerfragment newinstance(@nonnull integer initialarticleposition) {         articlecontainerfragment newfrag = new articlecontainerfragment();         //todo show facebook page article categories have 1         newfrag.articlestyle = articlestyle.main;         newfrag.initialarticleposition = initialarticleposition;         return newfrag;     }      @override     public int getmenuresourceid() {         return utils.no_menu;     }      @override     public void loadarticlesintoadapter(list<articleviewmodel> articleviewmodellist) {         adapter = getadapter(articleviewmodellist);         viewpager.setadapter(adapter);          if (initialarticleposition != null)             viewpager.setcurrentitem(initialarticleposition);          starttimer();     }      @override     public void updatecountertext(int currentquestion, int size) {         gettoolbar().setsubtitle(                 html.fromhtml(                         getstring(r.string.article_toolbar_subtitle_counter, getviewpagercurrentitem() + 1, size)                 )         );     }      @override     public int getviewpagercurrentitem() {         return viewpager.getcurrentitem();     }      @override     public int getarticletotalcount() {         return adapter.getcount();     }      @override     public void starttimer() {         timber.v("starting timer article");         subscription = observable.timer(getresources().getinteger(r.integer.number_of_seconds_until_article_is_considered_viewed), timeunit.seconds)                 .take(1)                 .subscribe(new subscriber<long>() {                     @override                     public void oncompleted() {                         timber.v("completed observing whether user reading article");                     }                      @override                     public void onerror(throwable e) {                         timber.e(e, "error observing whether user reading article");                     }                      @override                     public void onnext(long along) {                         presenter.userhasreadarticle();                     }                 });     }       @override     public void stoptimer() {         if (subscription != null) {             timber.v("stopping timer article");             subscription.unsubscribe();         }     }      @override     public string getcurrentarticlepermalink() {         return adapter.getitem(getviewpagercurrentitem())                 .getcurrentarticlepermalink();     }      @override     protected articlecontainercomponent oncreatenonconfigurationcomponent() {         return daggerarticlecontainercomponent.builder()                 .appcomponent(myapplication.getcomponent())                 .build();     }      @override     public void onviewcreated(view view, bundle savedinstancestate) {         getcomponent().inject(this);         super.onviewcreated(view, savedinstancestate);         initviewpager();     }      @override     public void ondestroyview() {         if (subscription != null)             subscription.unsubscribe();          super.ondestroyview();     }      @override     public int getlayoutresourceid() {         return r.layout.article_container_fragment;     }      @override     public void onresume() {         super.onresume();         starttimer();     }      @override     public void onpause() {         stoptimer();         super.onpause();     }      private void initviewpager() {         if (genericarticlecategory != null)             presenter.loadarticles(genericarticlecategory.getid());         else             presenter.loadallarticles();          viewpager.setoffscreenpagelimit(3);         viewpager.addonpagechangelistener(new viewpager.onpagechangelistener() {             @override             public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) {              }              @override             public void onpageselected(int position) {                 myanimationutils.showtoolbar(gettoolbar());                 presenter.pagechanged();              }              @override             public void onpagescrollstatechanged(int state) {              }         });     }      toolbar gettoolbar() {         if (toolbar == null)             toolbar = ((articleactivity) getactivity()).gettoolbar();          return toolbar;     }      public articleadapter getadapter(list<articleviewmodel> articleviewmodellist) {         if (articlestyle == articlestyle.main)             return new mainarticleadapter(getchildfragmentmanager(), articleviewmodellist);         else             return new unitsarticleadapter(getchildfragmentmanager(), articleviewmodellist);     } } 

here leakcanary log leak.

in com.example:1.0:1. * com.example.presentation.views.activities.articleactivity has leaked: * gc root thread java.lang.thread.<java local> (named 'rxcomputationthreadpool-1') * references java.util.concurrent.scheduledthreadpoolexecutor$delayedworkqueue.queue * references array java.util.concurrent.runnablescheduledfuture[].[0] * references java.util.concurrent.scheduledthreadpoolexecutor$scheduledfuturetask.callable * references java.util.concurrent.executors$runnableadapter.task * references rx.internal.schedulers.scheduledaction.action * references rx.internal.operators.onsubscribetimeronce$1.val$child (anonymous class implements rx.functions.action0) * references rx.internal.operators.operatortake$1.val$child (anonymous class extends rx.subscriber) * references rx.observers.safesubscriber.actual * references com.example.presentation.views.fragments.articlecontainerfragment$1.this$0 (anonymous class extends rx.subscriber) * references com.example.presentation.views.fragments.articlecontainerfragment.componentcache * leaks com.example.presentation.views.activities.articleactivity instance  * reference key: 2606e3f1-ad28-4727-b8d2-60e084c6389c * device: motorola google nexus 6 shamu * android version: 5.1.1 api: 22 leakcanary: 1.3.1 * durations: watch=5161ms, gc=161ms, heap dump=10786ms, analysis=24578ms  * details: * instance of java.lang.thread |   static $staticoverhead = byte[] [id=0x711bccc9;length=48;size=64] |   static max_priority = 10 |   static min_priority = 1 |   static nanos_per_milli = 1000000 |   static norm_priority = 5 |   static count = 14733 |   static defaultuncaughthandler = com.google.android.gms.analytics.exceptionreporter [id=0x12ea4500] |   contextclassloader = dalvik.system.pathclassloader [id=0x12c92de0] |   daemon = true |   group = java.lang.threadgroup [id=0x71058148] |   hasbeenstarted = true |   id = 14703 |   inheritablevalues = null |   interruptactions = java.util.arraylist [id=0x12f2b240] |   localvalues = null |   lock = java.lang.object [id=0x12f19c20] |   name = java.lang.string [id=0x12f2b220] |   nativepeer = -1264342016 |   parkblocker = java.util.concurrent.locks.abstractqueuedsynchronizer$conditionobject [id=0x12f0e100] |   parkstate = 3 |   priority = 5 |   stacksize = 0 |   target = java.util.concurrent.threadpoolexecutor$worker [id=0x12f25370] |   uncaughthandler = null * instance of java.util.concurrent.scheduledthreadpoolexecutor$delayedworkqueue |   static $staticoverhead = byte[] [id=0x12ee9401;length=8;size=24] |   static initial_capacity = 16 |   available = java.util.concurrent.locks.abstractqueuedsynchronizer$conditionobject [id=0x12f0e100] |   leader = java.lang.thread [id=0x12f22340] |   lock = java.util.concurrent.locks.reentrantlock [id=0x12f02fa0] |   queue = java.util.concurrent.runnablescheduledfuture[] [id=0x12efdf60;length=16] |   size = 3 * array of java.util.concurrent.runnablescheduledfuture[] |   [0] = java.util.concurrent.scheduledthreadpoolexecutor$scheduledfuturetask [id=0x12fe3f00] |   [1] = java.util.concurrent.scheduledthreadpoolexecutor$scheduledfuturetask [id=0x12c5e2c0] |   [2] = java.util.concurrent.scheduledthreadpoolexecutor$scheduledfuturetask [id=0x12c5e0c0] |   [3] = null |   [4] = null |   [5] = null |   [6] = null |   [7] = null |   [8] = null |   [9] = null |   [10] = null |   [11] = null |   [12] = null |   [13] = null |   [14] = null |   [15] = null * instance of java.util.concurrent.scheduledthreadpoolexecutor$scheduledfuturetask |   heapindex = 0 |   outertask = java.util.concurrent.scheduledthreadpoolexecutor$scheduledfuturetask [id=0x12fe3f00] |   period = 0 |   sequencenumber = 53 |   this$0 = java.util.concurrent.scheduledthreadpoolexecutor [id=0x12efde70] |   time = 32159209571737 |   callable = java.util.concurrent.executors$runnableadapter [id=0x1327bf20] |   outcome = null |   runner = null |   state = 0 |   waiters = null * instance of java.util.concurrent.executors$runnableadapter |   result = null |   task = rx.internal.schedulers.scheduledaction [id=0x1314fb80] * instance of rx.internal.schedulers.scheduledaction |   static $staticoverhead = byte[] [id=0x12d794e1;length=8;size=24] |   static serialversionuid = -3962399486978279857 |   action = rx.internal.operators.onsubscribetimeronce$1 [id=0x1327bef0] |   cancel = rx.internal.util.subscriptionlist [id=0x1327bf00] |   value = null * instance of rx.internal.operators.onsubscribetimeronce$1 |   this$0 = rx.internal.operators.onsubscribetimeronce [id=0x1314f7e0] |   val$child = rx.internal.operators.operatortake$1 [id=0x1310fd30] * instance of rx.internal.operators.operatortake$1 |   completed = false |   count = 0 |   this$0 = rx.internal.operators.operatortake [id=0x1327be60] |   val$child = rx.observers.safesubscriber [id=0x1310fd00] |   cs = rx.internal.util.subscriptionlist [id=0x1327bea0] |   op = null |   p = null |   requested = -9223372036854775808 * instance of rx.observers.safesubscriber |   actual = com.example.presentation.views.fragments.articlecontainerfragment$1 [id=0x1310fca0] |   done = false |   cs = rx.internal.util.subscriptionlist [id=0x1327be90] |   op = com.example.presentation.views.fragments.articlecontainerfragment$1 [id=0x1310fca0] |   p = null |   requested = -9223372036854775808 * instance of com.example.presentation.views.fragments.articlecontainerfragment$1 |   this$0 = com.example.presentation.views.fragments.articlecontainerfragment [id=0x130683a0] |   cs = rx.internal.util.subscriptionlist [id=0x1327be90] |   op = null |   p = null |   requested = -9223372036854775808 * instance of com.example.presentation.views.fragments.articlecontainerfragment |   adapter = com.example.presentation.views.adapters.mainarticleadapter [id=0x13131f40] |   articlestyle = com.example.presentation.views.enums.articlestyle [id=0x12f047c0] |   genericarticlecategory = null |   initialarticleposition = java.lang.integer [id=0x71054ef8] |   presenter = com.example.presentation.presenters.articlecontainerpresenter [id=0x13140b00] |   subscription = rx.observers.safesubscriber [id=0x1340ff70] |   toolbar = android.support.v7.widget.toolbar [id=0x130ba400] |   viewpager = null |   presenterdelegate = com.example.presentation.presenters.base.presentercontrollerdelegate [id=0x1327b7f0] |   componentcache = com.example.presentation.views.activities.articleactivity [id=0x12df6700] |   componentdelegate = com.example.presentation.presenters.base.componentcontrollerdelegate [id=0x1313fc60] |   componentfactory = com.example.presentation.presenters.base.componentcontrollerfragment$1 [id=0x1327b7e0] |   mactivity = null |   madded = false |   mallowentertransitionoverlap = null |   mallowreturntransitionoverlap = null |   manimatingaway = null |   marguments = null |   mbackstacknesting = 0 |   mcalled = true |   mcheckedforloadermanager = false |   mchildfragmentmanager = null |   mcontainer = null |   mcontainerid = 0 |   mdeferstart = false |   mdetached = false |   mentertransition = null |   mentertransitioncallback = null |   mexittransition = null |   mexittransitioncallback = null |   mfragmentid = 0 |   mfragmentmanager = null |   mfromlayout = false |   mhasmenu = false |   mhidden = false |   minlayout = false |   mindex = -1 |   minnerview = null |   mloadermanager = null |   mloadersstarted = false |   mmenuvisible = true |   mnextanim = 0 |   mparentfragment = null |   mreentertransition = java.lang.object [id=0x12e87aa0] |   mremoving = false |   mrestored = false |   mresumed = false |   mretaininstance = false |   mretaining = false |   mreturntransition = java.lang.object [id=0x12e87aa0] |   msavedfragmentstate = null |   msavedviewstate = null |   msharedelemententertransition = null |   msharedelementreturntransition = java.lang.object [id=0x12e87aa0] |   mstate = 0 |   mstateafteranimating = 0 |   mtag = null |   mtarget = null |   mtargetindex = -1 |   mtargetrequestcode = 0 |   muservisiblehint = true |   mview = null |   mwho = null * instance of com.example.presentation.views.activities.articleactivity |   static $staticoverhead = byte[] [id=0x12fc8001;length=24;size=40] |   static article_category_id_key = java.lang.string [id=0x130a3f00] |   static initial_article_to_load_key = java.lang.string [id=0x13083c20] |   static toolbar_title_key = java.lang.string [id=0x130a3f80] |   genericarticlecategory = null |   initialarticletoload = 0 |   toolbar = null |   toolbartitle = java.lang.string [id=0x12dfe9c0] |   delegate = com.example.presentation.presenters.base.componentcachedelegate [id=0x1327b190] |   mdelegate = android.support.v7.app.appcompatdelegateimplv14 [id=0x1342e560] |   mallloadermanagers = android.support.v4.util.simplearraymap [id=0x1314b3a0] |   mcheckedforloadermanager = true |   mcontainer = android.support.v4.app.fragmentactivity$2 [id=0x1327b180] |   mcreated = true |   mfragments = android.support.v4.app.fragmentmanagerimpl [id=0x130e4eb0] |   mhandler = android.support.v4.app.fragmentactivity$1 [id=0x1311fd80] |   mloadermanager = null |   mloadersstarted = false |   moptionsmenuinvalidated = false |   mreallystopped = true |   mresumed = false |   mretaining = false |   mstopped = true |   mactionbar = null |   mactivityinfo = android.content.pm.activityinfo [id=0x1322f400] |   mactivitytransitionstate = android.app.activitytransitionstate [id=0x12fa3380] |   mallloadermanagers = android.util.arraymap [id=0x1313fd00] |   mapplication = com.example.myapplication [id=0x12c93620] |   mcalled = true |   mchangecanvastotranslucent = false |   mchangingconfigurations = false |   mcheckedforloadermanager = true |   mcomponent = android.content.componentname [id=0x131881a0] |   mconfigchangeflags = 0 |   mcontainer = android.app.activity$1 [id=0x1327b150] |   mcurrentconfig = android.content.res.configuration [id=0x131fd580] |   mdecor = null |   mdefaultkeymode 

it doesn't leak activity strictly speaking. holds reference after unsubscribe time until observable returns flow control. full description of issue here: https://github.com/reactivex/rxjava/issues/1292.

basically observable hold reference subscriber until oncomplete, onerror or unsubscribe event processed. in case until observable.timer come sleep. since requesting unsubscribe before observable.timer finishes, processing of unsubscribe (releasing resources , null subscriber reference) delayed until event triggered.

so observable.timer holds reference subscriber holds reference fragment holds reference activity (articlecontainerfragment.componentcache). solution quite easy: don't hold reference activity @ subscribers long-running observables ever. create observable.timer inside of presenter , not fragment. or make fragment not hold reference activity.


Comments

Popular posts from this blog

c# - Better 64-bit byte array hash -

webrtc - Which ICE candidate am I using and why? -

php - Zend Framework / Skeleton-Application / Composer install issue -