原文:https://proandroiddev.com/everything-you-need-to-know-about-memory-leaks-in-android-d7a59faaf46a
Java语言的一个核心优势,或者更精确一点,这个JVM虚拟机的核心优势,是垃圾回收器,它可以让我们创建新的对象而不需要担心它的内存释放,它将负责释放内存.
我们也可以阻碍垃圾回收器去释放内存,如果我们不了解GC(垃圾回收器)的工作机制。
如果没有对gc工作原理搞懂,将会写出内存泄漏的代码,内存泄漏将会浪费app的内存,从而导致卡顿和内存溢出异常.
什么是内存泄漏
从内存中去释放不会使用的对象失败,就是内存泄漏
释放不会使用的对象失败意味着GC不能清理内存掉那些内存,如果GC不能清理掉我们就会有麻烦了,这块没有使用的对象将会占据内存空间一直到应用程序结束,或者某个方法结束.
直到方法结束?是的。我们有两种内存泄漏的情况,一种是一直占用内存空间直到应用程序结束,另外一种是占据内存空间到方法结束。前者应该容易明白,后者需要说明一下,让我来举个例子去描述这种情况,假设我们有方法X,方法X在后台做一个长时间的任务,它1分钟后才结束,方法X引用到了一个未使用的对象,在这种情况下,这种情况下未使用的对象内存将会占据,这一分钟内未使用的对象内存将不会释放,直到这个后台任务结束,GC才能清理掉它的内存.
一些基础
什么是内存
内存,或者随机访问内存,是一个在安卓设备或者计算机中用于存储当前运行应用的数据.
我打算去描述下在内存中两个主要的角色,堆和栈。
我不打算做太长的阐述,来说下关键点,一个简单描述,栈用来静态内存的分配而堆用来做动态内存的分配。要记住堆和栈都是存放在内存中的。
堆
Java堆内存被用来虚拟机分配对象使用。无论什么时候创建对象,它始终在堆创建,虚拟机,像JVM 和 DVM,运行规则的GC,释放掉没有引用的对象.
为了提供更好的用户体验,Android对于每个应用程序有一个限制的堆大小,这个限制的堆大小在不同大小ram手机上不同,如果你的app超过这个大小,将会抛出OutOfMemoryError然后被终止.
你曾经对应用程序的堆大小好奇吗?
我们来看看它们是多大,应用程序运行在DVM上,DVM是Java虚拟机针对移动设备的优化版本,它优化了虚拟机的内存,电池消耗,还有性能,它负责对每个应用程序分配内存.
来说下DVM中的两行参数:
dalvik.vm.heapgrowthlimit:
应用程序刚开始的堆内存大小,它是默认的堆内存大小,也是你应用最大的内存大小。
dalvik.vm.heapsize:
这是一个更大的堆内存大小,你可以在应用manifest中声明android:largeHeap=”true”获取。
通常不要使用这个参数获取更大的内存,除非你知道这个操作的副作用。
下面这个表格描述了不同ram设备应用程序的内存大小.
记住更大的ram你将会有更高的应用内存,
如何产看应用中分配的内存大小
通过ActivityManager的getMemoryClass()和getLargeMemoryClass()将分别获得,默认的堆内存大小,和最大的堆内存大小.
真实的代码怎么分配内存
下面简单的案例来说明怎么分配内存
下面的图展示了应用程序启动后,堆栈内存的分配情况
我们将会回顾应用的执行和停止,描述什么时候分配对象,在堆栈中存储,我们也会看堆栈释放内存的情况.
行1 :JVM创建一个栈内存块对于main方法.
行2 :在这一行中,我们创建了原始本地变量,这个变量将会被创建存放main方法栈内存中
行3 :这里我需要你注意,在这行中,我们创建了一个新的对象,这个对象在main方法的栈中被创建,然后存储在堆中,栈存放了该对象的引用,就是堆中该对象的地址,堆中存储了原始对象.
行4 :和行3是一样的.
行5 :JVM为foo方法创建一个栈内存块
行6 :创建一个新的对象,这个对象被创建在foo方法的栈内存中,它存放一个地址指向堆中第三行创建的对象。这个第三行创建的对象在堆中的地址我们通过行5传递过来,始终记住java始终传递的是引用.
行7 :我们将创建一个新的对象,这个对象被创建在栈中,然后指到堆的string pool中
行8 :foo方法的最后一行,栈中存储foo方法块将会被释放调
行9 :和行8是一样的main方法结束,栈中存储main方法块将会被释放调
方法结束后相应的栈内存被释放掉
该图表示了foo方法终止后,foo方法的栈内存将会被释放和回收
这是相同的,当main方法结束掉后,main方法的栈内存将会被释放和回收
结论:
现在,栈内存中的对象是临时的,一旦方法终止,对象将会被释放和回收。
栈是一个LIFO数据结构 (Last-In-First-Out),你可以把它看成一个盒子,通过这种结构,程序可以简单的管理通过push和pop.
每次你需要去存储像一个变量或者方法它会push进去,然后栈针上移,从方法中退出,pop操作后,栈针指向的内存下移。
堆内存的释放
与栈不同的是,释放和回收来自堆的对象,我们需要帮助。
对于Java,或者更精确点来说,这个Jvm有一个超级英雄的角色帮助我们。我们称它为垃圾回收器。它会为我们努力工作。发现未使用的对象,释放它们,然后省出更多的内存.
垃圾回收器如何释放堆内存
垃圾回收器将会回收掉没有引用的对象,当这里一个对象在堆没有任何引用到它,垃圾回收器将会释放它,开拓更多的空间。
GC root是JVM引用到的对象。它是这棵树的初始对象。每个对象都会有一个或者更多root对象。只要应用或者GC roots可以引用那些roots或者那些对象,整个数是可获得的。一旦它们没有被application或者GC获取到,将认为它们是不能获取的对象(无用的对象).
垃圾回收器触发后将会发生什么?
对于现在,当前内存中栈的状态被清空了,堆充满了未使用的对象.
在运行GC后将会变成
GC将会释放和清除所有未使用的对象从堆中。
内存泄漏是如何导致的
内存泄漏当栈中仍然引用了在堆中未使用的对象。详情见下图
从图中我们可以看到一些无用对象仍然有引用。垃圾回收器回收不到它们。