Why are streams flowing on Android?

I noticed in our Android application that every time we go to the main screen, we increase the size of the heap (leak) by the number of ByteArrayOutputStream. The best I could handle was to add

this.mByteArrayOutputStream = null; 

at the end of run () to prevent the heap from growing continuously. If anyone could enlighten me, I would be very grateful. I wrote the following example to illustrate the problem.

MainActivity.java

 public class MainActivity extends Activity { private Controller mController; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } @Override protected void onStart() { super.onStart(); this.mController = new Controller(); mController.connect(); } @Override protected void onStop() { super.onStop(); mController.quit(); } } 

Controller.java

 public class Controller { public volatile ReaderThread mThread; public Controller() { super(); } public void connect() { mThread = new ReaderThread("ReaderThread"); mThread.start(); } public void quit() { mThread.quit(); } public static class ReaderThread extends Thread { private volatile boolean isProcessing; private ByteArrayOutputStream mByteArrayOutputStream; public ReaderThread(String threadName) { super(threadName); } @Override public void run() { this.isProcessing = true; Log.d(getClass().getCanonicalName(), "START"); this.mByteArrayOutputStream = new ByteArrayOutputStream(2048000); int i = 0; while (isProcessing) { Log.d(getClass().getCanonicalName(), "Iteration: " + i++); mByteArrayOutputStream.write(1); try { Thread.sleep(1000); } catch (InterruptedException e) { } } try { mByteArrayOutputStream.reset(); mByteArrayOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } Log.d(getClass().getCanonicalName(), "STOP"); } public void quit() { this.isProcessing = false; } } } 
+4
source share
5 answers

Threads are immune to GC because they are the roots of garbage collectors . Thus, it is likely that the JVM stores your ReaderThread in memory along with its allocations for member variables, thereby creating a leak.

Disabling ByteArrayOutputStream , as you noted, will make its buffered data (but not ReaderThread itself) available to the GC.

EDIT

After some disconnection, we found out that the Android debugger was causing a perceived leak :

The VM ensures that any object that the debugger is aware of does not collect garbage until the debugger shuts down. This can cause objects to accumulate over time when the debugger is connected . For example, if the debugger sees the current thread, the associated Thread object does not collect garbage even after the thread finishes.

+8
source

On the Android Debugging page:

The debugger and the garbage collector are currently freely integrated. The VM ensures that any object that the debugger is aware of is not garbage collected until the debugger shuts down. This can lead to the accumulation of objects over time, while the debugger is associated. For example, if the debugger sees the current thread, the associated Thread object does not collect garbage even after the thread terminates.

This reduces the value of DDMS heap monitoring, what do you think?

+3
source

This problem is quite interesting. I looked at him and managed to get a working solution without leaks. I replaced mutable variables with my atomic counterparts. I also used AsyncTask instead of Thread. This seems to be a trick. No more leaks.

code

 class Controller { public ReaderThread mThread; private AtomicBoolean isProcessing = new AtomicBoolean(false); public Controller() { super(); } public void connect() { ReaderThread readerThread = new ReaderThread("ReaderThread",isProcessing); readerThread.execute(new Integer[0]); } public void quit() { isProcessing.set(false); } public static class ReaderThread extends AsyncTask<Integer, Integer, Integer> { private AtomicBoolean isProcessing; private ByteArrayOutputStream mByteArrayOutputStream; public ReaderThread(String threadName, AtomicBoolean state) { this.isProcessing = state; } public void run() { this.isProcessing.set(true); Log.d(getClass().getCanonicalName(), "START"); this.mByteArrayOutputStream = new ByteArrayOutputStream(2048000); int i = 0; while (isProcessing.get()) { Log.d(getClass().getCanonicalName(), "Iteration: " + i++); mByteArrayOutputStream.write(1); try { Thread.sleep(1000); } catch (InterruptedException e) { } } try { mByteArrayOutputStream.reset(); mByteArrayOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } Log.d(getClass().getCanonicalName(), "STOP"); } public void quit() { this.isProcessing.set(false); } @Override protected Integer doInBackground(Integer... params) { run(); return null; } } } 

Here is a comparison of both code samples after they hit the main screen and from it twice. You can see the byte [] leak on the first.

Hprof comparison of the solutions

Regarding this, google recommends using AsyncTask or a service for async operations. I remember one of my videos that clearly consulted with Threads. The person making the presentation warned of side effects. Think this is one of them? I can clearly see that there is a leak when the activity comes back to life, but there is no explanation of the reasons for the leak (at least on the JVM. I do not know about the internal elements of dalvik VM and when it considers that the threads have completely stopped). I tried weak references / nulling the controller / etc and none of these approaches worked. C>

See this answer for what this leak is being reported for - fooobar.com/questions/945895 / .... This is a false result.

I managed to get rid of the memory occupied by BAOS, without explicitly eliminating it using asynchronous tasks. It should also work for other variables. Let me know if this solves it for you.

+2
source

Based on the code you showed, an instance of ByteArrayOutputStream can only leak when a ReaderThread instance ReaderThread . And this can only happen if either the stream is still working, or there is a link somewhere available.

Focus on how the ReaderThread instance ReaderThread .

+1
source

I had a similar problem, and I decided to reset the thread to zero.

I tried my code in an emulator (SDK 16), changing the quit() method as follows:

 public void quit() { mThread.quit(); mThread = null; // add this line } 

I checked in DDMS that the leak effectively stopped. Removing the line, the leak is returning.

- EDITED -

I tried this also in a real device using SDK 10 and I set the results below.

Did you try to reset the thread in the test code or in your full code? He throws a test code, does not flow after zeroing the thread, neither in the real device, nor in the emulator.

Screenshot after initial launch (7.2 MB allocated / used: 4.6 MB):

Step1

Screenshot after several restarts (7.2 MB allocated / used: 4.6 MB):

Step2

Screenshot after several very fast restarts and rotational time of the device server (allocated 13.2 MB / used: 4.6 MB):

Step3

Although the memory allocated on the last screen is much higher than the original memory, the allocated memory remains 4.6 MB, so it does not leak.

+1
source

Source: https://habr.com/ru/post/945895/


All Articles