Android memory leak summary
The purpose of memory management is to help us effectively avoid memory leaks in our applications during development. Everyone is familiar with memory leaks. To put it simply, it means that the object that should be released has not been released, and has been held by a certain instance but is no longer used, so that the GC cannot be recycled. Recently, I have read a lot of relevant documents and materials. I plan to summarize and settle them and share and learn with you, and also give myself a warning about how to avoid these situations during coding in the future and improve the experience and quality of the application.
I will start with the basics of java memory leaks, and use specific examples to illustrate the various causes of Android's memory leaks, as well as how to use tools to analyze application memory leaks, and finally summarize them.
Java memory allocation strategy
There are three types of memory allocation strategies when Java programs run, namely static allocation, stack allocation, and heap allocation. Correspondingly, the memory space used by the three storage strategies is mainly static storage area (also known as method area), stack area and heap area.
Static storage area (method area): mainly stores static data, global static data and constants. This piece of memory is allocated when the program is compiled and exists throughout the program run.
Stack area: When a method is executed, local variables in the method body (including the basic data type and object reference) are created on the stack, and the memory held by these local variables will be automatically released at the end of the method execution. Because the stack memory allocation operation is built into the instruction set of the processor, it is very efficient, but the allocated memory capacity is limited.
Heap area: also known as dynamic memory allocation, usually refers to the memory that is directly new when the program is running, that is, an instance of the object. When this part of the memory is not in use, the Java garbage collector will be responsible for recycling.
The difference between stack and heap:
Some basic types of variables defined in the method body and reference variables of objects are allocated in the method's stack memory. When a variable is defined in a block of method, Java will allocate memory space for the variable on the stack. When the scope of the variable is exceeded, the variable will be invalid, and the memory space allocated to it will be freed, and the memory space can be reused.
Heap memory is used to store all objects created by new (including all member variables in the object) and arrays. The memory allocated in the heap will be automatically managed by the Java garbage collector. After an array or object is generated in the heap, a special variable can be defined in the stack. The value of this variable is equal to the first address of the array or object in the heap memory. This special variable is the reference variable we mentioned above. We can access objects or arrays in the heap through this reference variable.
For example:
public class Sample {int s1 = 0;Sample mSample1 = new Sample();public void method() {int s2 = 1;Sample mSample2 = new Sample();}}Sample mSample3 = new Sample(); The local variable s2 of the Sample class and the reference variable mSample2 both exist on the stack, but the object pointed to by mSample2 exists on the heap.
The object entity pointed to by mSample3 is stored on the heap, including all member variables s1 and mSample1 of this object, and it exists itself in the stack.
in conclusion:
The basic data types and references of local variables are stored in the stack, and the referenced object entities are stored in the heap. - Because they belong to variables in methods, the life cycle ends with methods.
Member variables are all stored and in the heap (including basic data types, referenced and referenced object entities) - because they belong to classes, class objects will eventually be used for new use.
After understanding Java's memory allocation, let's take a look at how Java manages memory.
How Java manages memory
Java's memory management is the issue of object allocation and release. In Java, programmers need to apply for memory space for each object through the keyword new (except for basic types), and all objects allocate space in the heap (Heap). In addition, the release of objects is determined and executed by the GC. In Java, memory allocation is done by programs, while memory release is done by GC. This method of revenue and expenditure on two lines does simplify the work of programmers. But at the same time, it also adds to the work of the JVM. This is also one of the reasons why Java programs run slower. Because, in order to correctly release objects, GC must monitor the running status of each object, including the application, citation, citation, assignment, etc. of the object, and GC needs to monitor it.
Monitoring the state of an object is to release the object more accurately and in a timely manner, and the fundamental principle of releasing the object is that the object is no longer referenced.
To better understand how GC works, we can consider the object as a vertex of a directed graph, and the reference relationship as directed edges of the graph, which point from the referencer to the referenced object. In addition, each thread object can be used as the starting vertex of a graph. For example, most programs start from the main process, so the graph is a root tree starting with the main process vertex. In this directed graph, objects reachable by the root vertex are valid objects, and GC will not recycle these objects. If an object (connected subgraph) is unreachable from this root vertex (note that the graph is a directed graph), then we believe that this (those) object is no longer referenced and can be recycled by GC.
Below, we give an example of how to use directed graphs to represent memory management. For every moment of the program, we have a directed graph representing the memory allocation of the JVM. The picture below is a diagram of the program on the left running to line 6.
Java uses directed graphs for memory management, which can eliminate the problem of reference loops. For example, there are three objects that refer to each other. As long as they and the root process are unreachable, GC can also recycle them. The advantage of this method is that it has high precision in managing memory, but is low efficiency. Another commonly used memory management technology is to use counters. For example, the COM model uses the counter method to manage components. Compared with directed graphs, it has low precision lines (it is difficult to deal with circular reference problems), but it has high execution efficiency.
What is a memory leak in Java
In Java, memory leaks are the existence of some allocated objects, which have the following two characteristics. First, these objects are reachable, that is, in the directed graph, there are paths that can be connected to them; secondly, these objects are useless, that is, the program will not use these objects again in the future. If the object meets these two conditions, these objects can be determined to be a memory leak in Java, and these objects will not be recycled by GC, but it occupies memory.
In C++, memory leaks have a larger range. Some objects are allocated memory space, but then unreachable. Since there is no GC in C++, these memory will never be collected. In Java, these unreachable objects are recycled by GC, so programmers do not need to consider this part of memory leak.
Through analysis, we know that for C++, programmers need to manage edges and vertices by themselves, while for Java programmers, they only need to manage edges (no need to manage the release of vertices). In this way, Java improves programming efficiency.
Therefore, through the above analysis, we know that there are also memory leaks in Java, but the scope is smaller than that of C++. Because Java language guarantees that any object is reachable, all unreachable objects are managed by GC.
For programmers, GC is basically transparent and invisible. Although we only have a few functions to access GC, such as System.gc(), which runs GC, according to the Java language specification definition, this function does not guarantee that the JVM's garbage collector will execute. Because, different JVM implementers may use different algorithms to manage GC. Generally, GC's threads have lower priority. There are many strategies for JVM to call GC. Some of them only start working when the memory usage reaches a certain level. Some execute them regularly. Some execute GC smoothly, and some execute GC in interrupt manner. But generally speaking, we don't need to care about this. Unless in some specific situations, the execution of GC affects the performance of the application. For example, for real-time web-based systems such as online games, users do not want GC to suddenly interrupt application execution and perform garbage collection, then we need to adjust the parameters of GC so that GC can free memory in a smooth manner, such as decomposing garbage collection into a series of small steps to execute. The HotSpot JVM provided by Sun supports this feature.
Also gives a typical example of Java memory leak.
Vector v = new Vector(10);for (int i = 1; i < 100; i++) {Object o = new Object();v.add(o);o = null; }In this example, we apply for the Object object cycle and put the applied object into a Vector. If we only release the reference itself, the Vector still references the object, so this object is not recyclable for GC. Therefore, if the object has to be deleted from the Vector after it is added to the Vector, the easiest way is to set the Vector object to null.
Memory leak in detailed Java
1. Java memory recycling mechanism
Regardless of the memory allocation method of any language, it is necessary to return the real address of the allocated memory, that is, return a pointer to the first address of the memory block. Objects in Java are created using new or reflection methods. The creation of these objects is allocated in the heap. All objects are collected by the Java virtual machine through a garbage collection mechanism. In order to correctly release objects, GC will monitor the health status of each object and monitor their application, citation, citation, assignment, etc. Java will use directed graph methods to manage memory to monitor whether the object can be achieved in real time. If it is not reached, it will be recycled, which can also eliminate the problem of reference loops. In Java language, there are two types of memory space that determines whether a memory space meets the garbage collection criteria: one is to assign an empty value to the object, which has not been called below, and the other is to assign a new value to the object, thus reallocating the memory space.
2. Causes of Java memory leak
Memory leak refers to the continuous useless object (object that is no longer used) or the memory of useless objects cannot be released in time, resulting in waste of memory space, which is called a memory leak. Memory leaks are sometimes not serious and not easy to detect, so developers do not know that there is a memory leak, but sometimes it can be very serious and will prompt you to Out of memory.
What is the root cause of Java memory leak? If a long-life cycle object holds a reference to a short-life cycle object, it is likely that a memory leak will occur. Although a short-life cycle object is no longer needed, it cannot be recycled because it holds its reference for a long-life cycle. This is the scenario where memory leaks occur in Java. There are mainly the following categories:
1. Static collection class causes memory leak:
The use of HashMap, Vector, etc. is most likely to occur in memory leaks. The life cycle of these static variables is consistent with that of the application. All objects they reference cannot be released because they will also be referenced by Vector, etc.
For example
Static Vector v = new Vector(10);for (int i = 1; i<100; i++){Object o = new Object();v.add(o);o = null;}In this example, the Object object is applied loop and the applied object is placed into a Vector. If the reference itself is only released (o=null), the Vector still references the object, so this object is not recyclable for GC. Therefore, if the object has to be deleted from the Vector after it is added to the Vector, the easiest way is to set the Vector object to null.
2. When the object properties in the collection are modified, the remove() method will not work.
For example:
public static void main(String[] args){Set<Person> set = new HashSet<Person>();Person p1 = new Person("Tang Monk","pwd1",25);Person p2 = new Person("Sun Wukong","pwd2",26);Person p3 = new Person("Zhu Bajie","pwd3",27);set.add(p1);set.add(p2);set.add(p3);System.out.println("There are a total of:"+set.size()+" elements!"); //Result: There are a total of: 3 Elements!p3.setAge(2); //Modify the age of p3, and the hashcode value corresponding to the p3 element changes at this time set.remove(p3); //Remove it at this time, causing memory leakage set.add(p3); //Add it again and it was successfully added System.out.println("There are:"+set.size()+" elements!"); //Result: There are: 4 elements in total! for (Person person : set){System.out.println(person);}}3. Listener
In Java programming, we all need to deal with listeners. Usually, a lot of listeners are used in an application. We will call a control method such as addXXXListener() to add listeners, but often when releasing the object, we do not remember to delete these listeners, thereby increasing the chance of memory leaks.
4. Various connections
For example, database connection (dataSourse.getConnection()), network connection (socket) and io connections will not be automatically recycled by GC unless it explicitly calls its close() method to close its connection. The Resultset and Statement objects can not be explicitly recycled, but the Connection must be explicitly recycled because the Connection cannot be automatically recycled at any time. Once the Connection is recycled, the Resultset and Statement objects will be immediately NULL. However, if you use a connection pool, the situation is different. In addition to explicitly closing the connection, you must also explicitly close the Resultset Statement object (closing one of them, the other one will also be closed), otherwise a large number of Statement objects will not be released, causing memory leaks. In this case, the connection will usually be released in the try and finally.
5. References to internal classes and external modules
References to internal classes are relatively easy to forget, and once they are not released, a series of successor class objects may not be released. In addition, programmers should also be careful of inadvertent references to external modules. For example, programmer A is responsible for module A and calls a method of module B such as:
public void registerMsg(Object b);
This kind of call requires great care. When an object is passed in, it is very likely that module B will keep a reference to the object. At this time, you need to pay attention to whether module B provides corresponding operations to remove references.
6. Singleton mode
Incorrect use of singleton pattern is a common problem that causes memory leaks. Singleton objects will exist throughout the entire life cycle of the JVM after initialization (in the form of static variables). If the singleton object holds external references, then this object will not be recycled normally by the JVM, resulting in memory leaks. Consider the following example:
class A{public A(){B.getInstance().setA(this);}....}//Class B uses the singleton mode class B{private A a;private static B instance=new B();public B(){}public static B getInstance(){return instance;}public void setA(A a){this.a=a;}//getter...}Obviously B adopts the singleton pattern, which holds a reference to an object A, and the object of this class A will not be recycled. Imagine what would happen if A was a more complex object or collection type
Summary of common memory leaks in Android
Collection class leak
If the collection class only has a method to add elements and does not have a corresponding deletion mechanism, the memory will be occupied. If this collection class is a global variable (such as static properties in the class, global map, etc., that is, there is a static reference or final pointing to it all the time), then there is no corresponding deletion mechanism, which may cause the memory occupied by the collection to only increase and not decrease. For example, the typical example above is one of these situations. Of course, we will definitely not write such 2B code in the project, but it is still easy to happen if we are not careful. For example, we all like to do some caches through HashMap, so we should be more careful in this situation.
Memory leak caused by singletons
Because the static nature of a singleton makes its life cycle as long as the application's life cycle, if used inappropriately, it is easy to cause memory leakage. For example, the following typical example,
public class AppManager {private static AppManager instance;private Context context;private AppManager(Context context) {this.context = context;}public static AppManager getInstance(Context context) {if (instance == null) {instance = new AppManager(context);}return instance;}}This is a normal singleton pattern. When creating this singleton, since a Context needs to be passed in, the length of the life cycle of this Context is crucial:
1. If the Context of Application is passed in at this time, because the life cycle of Application is the life cycle of the entire application, there will be no problem.
2. If the Activity Context is passed in at this time, when the Activity corresponding to this Context exits, since the reference to the Context is held by a singleton object, its life cycle is equal to the entire application life cycle, so when the Activity exits, its memory will not be recycled, which causes a leak.
The correct way should be changed to the following:
public class AppManager {private static AppManager instance;private Context context;private AppManager(Context context) {this.context = context.getApplicationContext();// context using Application}public static AppManager getInstance(Context context) {if (instance == null) {instance = new AppManager(context);}return instance;}}Or write this way, and you don't even need to pass the Context in:
Add a static method to your Application, getContext() returns the context of the Application.
...
context = getApplicationContext();.../*** Get global context* @return Return global context object*/public static Context getContext(){return context;}public class AppManager {private static AppManager instance;private Context context;private AppManager() {this.context = MyApplication.getContext();// context}public static AppManager getInstance() {if (instance == null) {instance = new AppManager();}return instance;}}Anonymous inner classes/non-static inner classes and asynchronous threads
Memory leak caused by creating static instances in non-static internal classes
Sometimes we may start activities frequently. In order to avoid repeatedly creating the same data resources, this way of writing may occur:
public class MainActivity extends AppCompatActivity {private static TestResource mResource = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if(mManager == null){mManager = new TestResource();}//...}class TestResource {//...}}This creates a singleton of a non-static inner class inside the Activity, and the data of the singleton is used every time the Activity is started. Although the repeated creation of resources is avoided, this writing will cause memory leaks, because the non-static inner class will hold references to external classes by default, and the non-static inner class will create a static instance, and the life cycle of the instance is as long as the application, which causes the static instance to always hold references to the Activity, resulting in the Activity's memory resources that cannot be recycled normally. The correct way to do it is:
Set the inner class as a static inner class or extract the inner class and encapsulate it into a singleton. If you need to use Context, please follow the above recommended Context to use Application. Of course, the context of Application is not omnipotent, so it cannot be used randomly. In some places, you must use the Context of Activity. The application scenarios of Context of Application, Service, and Activity are as follows:
Where: NO1 means that Application and Service can start an activity, but a new task task queue needs to be created. For Dialog, it can only be created in Activity
Anonymous internal class
Android development often inherits the implementation of Activity/Fragment/View. At this time, if you use anonymous classes and are held by asynchronous threads, be careful. If there is no measure, it will definitely lead to leakage.
public class MainActivity extends Activity {...Runnable ref1 = new MyRunable();Runnable ref2 = new Runnable() {@Overridepublic void run() {}};...}The difference between ref1 and ref2 is that ref2 uses anonymous inner classes. Let's take a look at the memory referenced at runtime:
As you can see, ref1 is nothing special.
But there is an additional reference in the implementation object of the anonymous class ref2:
This $0 reference points to MainActivity.this, that is, the current MainActivity instance will be held by ref2. If this reference is passed into an asynchronous thread, and this thread and this Activity life cycle are inconsistent, the Activity leak will occur.
Memory leak caused by Handler
The memory leak problem caused by the use of Handler should be said to be the most common. In order to avoid ANR, we do not perform time-consuming operations on the main thread, and use the Handler to handle network tasks or encapsulate some request callbacks and other APIs. However, Handler is not omnipotent. If the code of the Handler is written in a standardized manner, it may cause memory leaks. In addition, we know that Handler, Message and MessageQueue are all related to each other. In case the Message sent by Handler has not been processed yet, the Message and the Handler object that sent it will be held by the thread MessageQueue.
Since Handler belongs to TLS (Thread Local Storage) variables, the life cycle and activity are inconsistent. Therefore, this implementation method is generally difficult to ensure that it is consistent with the life cycle of the View or Activity, so it is easy to cause the correct release.
For example:
public class SampleActivity extends Activity {private final Handler mLeakyHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// ...}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Post a message and delay its execution for 10 minutes.mLeakyHandler.postDelayed(new Runnable() {@Overridepublic void run() { /* ... */ }}, 1000 * 60 * 10);// Go back to the previous Activity.finish();}}A message Message delayed execution of 10 minutes is declared in the SampleActivity, and mLeakyHandler pushes it into the message queue MessageQueue. When the Activity is dropped by finish(), the Message that delays execution of the task will continue to exist in the main thread, which holds the Handler reference of the Activity, so the Activity dropped by finish() will not be recycled, causing memory leakage (because the Handler is a non-static inner class, it will hold references to the external class, which refers to SampleActivity here).
Fix: Avoid using non-static inner classes in Activity. For example, if we declare the Handler as static above, its survival period has nothing to do with the life cycle of the Activity. At the same time, the Activity is introduced through weak references to avoid directly passing the Activity as a context. See the following code:
public class SampleActivity extends Activity {/*** Instances of static inner classes do not hold an implicit* reference to their outer class.*/private static class MyHandler extends Handler {private final WeakReference<SampleActivity> mActivity;public MyHandler(SampleActivity activity) {mActivity = new WeakReference<SampleActivity>(activity);}@Overridepublic void handleMessage(Message msg) {SampleActivity activity = mActivity.get();if (activity != null) {// ...}}}private final MyHandler mHandler = new MyHandler(this);/*** Instances of anonymous classes do not hold an implicit* reference to their outer class when they are "static".*/private static final Runnable sRunnable = new Runnable() {@Overridepublic void run() { /* ... */ }};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Post a message and delay its execution for 10 minutes.mHandler.postDelayed(sRunnable, 1000 * 60 * 10);// Go back to the previous Activity.finish();}}Overview, it is recommended to use static inner class + WeakReference. Be careful to be empty before each use.
WeakReference was mentioned earlier, so here I will briefly talk about several reference types of Java objects.
Java has four categories of references: Strong reference, SoftReference, WeakReference, and PhatomReference.
In the development of Android applications, in order to prevent memory overflow, when dealing with some objects that occupy large memory and have a long declaration cycle, soft reference and weak reference technologies can be used as much as possible.
Soft/weak references can be used in conjunction with a reference queue (ReferenceQueue). If the object referenced by the soft reference is recycled by the garbage collector, the Java virtual machine will add the soft reference to the associated reference queue. This queue allows you to know the recycled list of soft/weak references, thus clearing the buffer that has failed soft/weak references.
Suppose our application will use a large number of default images, such as the default avatar, default game icon, etc., which will be used in many places. If you read the picture every time, it will be slower because the reading of the file requires hardware operation, which will lead to lower performance. So we consider cache the image and read it directly from memory when needed. However, since images take up a lot of memory space and cache many images requires a lot of memory, OutOfMemory exceptions may be more likely to occur. At this time, we can consider using soft/weak reference techniques to avoid this problem. The following is the prototype of the cache:
First define a HashMap and save the soft reference object.
private Map <String, SoftReference<Bitmap>> imageCache = new HashMap <String, SoftReference<Bitmap>> ();
Let’s define a method to save the soft reference of Bitmap to HashMap.
After using soft references, before the OutOfMemory exception occurs, the memory space of these cached image resources can be freed, thereby preventing the memory from reaching the upper limit and avoiding Crash.
If you just want to avoid the occurrence of OutOfMemory exception, you can use soft references. If you care more about the performance of your application and want to recycle some objects that occupy more memory as soon as possible, you can use weak references.
In addition, you can determine whether the object is frequently used to determine whether it is selected for soft reference or weak reference. If the object may be used frequently, try to use soft references. If the object is not used more likely, it can be used with weak references.
OK, keep going back to the topic. As mentioned earlier, create a static Handler inner class and use weak references to the objects held by the Handler, so that the objects held by the Handler can also be recycled during recycling. However, although this avoids Activity leakage, there may still be pending messages in the message queue of the Looper thread, so we should remove the messages in the message queue MessageQueue during Destroy or Stop of the Activity.
The following methods can remove the Message:
public final void removeCallbacks(Runnable r); public final void removeCallbacks(Runnable r, Object token); public final void removeCallbacksAndMessages(Object token); public final void removeMessages(int what); public final void removeMessages(int what, Object object);
Try to avoid using static member variables
If a member variable is declared static, we all know that its life cycle will be the same as the entire app process life cycle.
This will cause a series of problems. If your app process is designed to be memory-resident, then even if the app cuts to the background, this part of the memory will not be released. According to the current memory management mechanism of mobile apps, background processes that account for a large amount of memory will be recycled first. If this app has done mutual protection of processes, it will cause the app to restart frequently in the background. When the phone installs the app you participated in the development, the phone consumes power and traffic overnight, and your app has to be uninstalled or silent by the user.
The fix here is:
Do not initialize static members at the beginning of the class. Lazy initialization can be considered.
In architectural design, we should think about whether it is really necessary to do this and try to avoid it. If the architecture needs to be designed like this, then you have the responsibility to manage the life cycle of this object.
Avoid override finalize()
1. The finalize method is executed at an uncertain time and cannot be relied on to release scarce resources. The reasons for uncertain time are:
The time when the virtual machine calls GC is uncertain
The time when the Finalize daemon thread is scheduled is uncertain
2. The finalize method will only be executed once. Even if the object is resurrected, if the finalize method has been executed, it will not be executed again when it is GC again. The reason is:
The object containing the finalize method generates a finalize reference by the virtual machine when new, and references to the object. When the finalize method is executed, the finalize reference corresponding to the object will be released. Even if the object is resurrected at this time (that is, referencing the object with a strong reference), and the second time it is GC, since the finalize reference is no longer corresponding to it, the finalize method will not be executed.
3. An object containing the Finalize method needs to go through at least two rounds of GC before it can be released.
Memory leak caused by unclosed resource
For the use of resources such as BraodcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap, etc., it should be closed or cancelled in time when the Activity is destroyed, otherwise these resources will not be recycled, causing memory leakage.
Memory pressure caused by some bad code
Some codes do not cause memory leakage, but they either do not release unused memory in time, or do not effectively utilize existing objects but frequently apply for new memory.
for example:
Bitmap does not call the recycle() method. When the Bitmap object is not used, we should call recycle() first to free up memory before it is set to null. Because the memory space of the Bitmap object is loaded, part of it is Java and part of C (because the underlying layer of Bitmap allocation is called through JNI). And this recyle() is for the memory release of the C part.
When constructing Adapter, no cached convertView is used, and a new convertView is created every time. ViewHolder is recommended here.
Summarize
References to components such as Activity should be controlled within the life cycle of the Activity; if you cannot, consider using getApplicationContext or getApplication to avoid the activity being leaked by external long-lived objects.
Try not to use non-static external member variables (including context) in static variables or static inner classes. Even if you want to use them, consider emptying the external member variables at the right time; you can also use weak references to refer to variables from external classes in the inner classes.
For inner class objects with longer life cycle than Activity, and member variables of external classes are used in inner class, this can be done to avoid memory leaks:
Change the inner class to a static inner class
Use weak references to refer to member variables of external classes in static inner classes
It is best to use weak references to the reference object held by the Handler, and the messages in the Handler can be cleared when the resource is released. For example, when Activity onStop or onDestroy is on, cancel the Message and Runnable of the Handler object.
In the implementation of Java, it is also necessary to consider the release of its object. The best way is to explicitly assign the object to null when not using an object. For example, after using Bitmap, call recycle() first, then assign it to null, and clear an array with direct or indirect references to resources such as images (using array.clear(); array = null), etc. It is best to follow the principle of who creates and who releases it.
To correctly close the resources, for the use of BraodcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap and other resources, it should be closed or cancelled in time when the Activity is destroyed.
Stay sensitive to the life cycle of objects, pay special attention to the life cycle of singletons, static objects, global sets, etc.
The above is a summary of the causes of memory leaks in Java introduced to you by the editor and how to avoid memory leaks (super detailed version). I hope it will be helpful to everyone. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support to Wulin.com website!