任何程序都会涉及到内存的操作,即使java本身自带内存回收机制,当我们程序操作不当的时候,仍然会出现各种难以预料的异常情况。
java的内存管理机制
Java是在JVM所虚拟出的内存环境中运行的。内存分为栈(stack)和堆(heap)两部分。
栈
栈用于储存方法的调用,每一个线程都有一个栈。栈中的每个储存单元都保存有该方法调用的参数、局部变量和返回地址,参数和局部变量只能是基本类型或对象的引用。当被调用方法运行结束时,该方法对应的帧将被删除,参数和局部变量所占据的空间也随之释放。线程回到原方法,继续执行。当所有的栈都清空时,程序也随之运行结束。
堆
堆主要用于储存对象。与栈不同,堆的空间不会随着方法调用结束而清空。因此java垃圾回收时,主要回收的是堆中的内存。
垃圾回收机制
- 垃圾回收可以自动清除掉对中的无用对象。
- 垃圾回收器如何判断一个对象是否可以回收:首先以栈和static数据为根(root),从根出发,需找该对象的索引链,如果该对象能够被索引到,则为可达对象,否则为不可达对象。所有不可达对象将会被垃圾回收期回收。
垃圾回收内存方法
mark and sweep : 这种机制下,每个对象将有标记信息,用于表示该对象是否可到达。当垃圾回收启动时,Java程序暂停运行。JVM从根出发,找到所有的可到达对象,并标记(mark)。随后,JVM需要扫描整个堆,找到剩余的对象,并清空这些对象所占据的内存。
copy and sweep : 这种机制下,堆被分为两个区域。对象总存活于两个区域中的一个。当垃圾回收启动时,Java程序暂停运行。JVM从根出发,找到可到达对象,将可到达对象复制到空白区域中并紧密排列,修改由于对象移动所造成的引用地址的变化。最后,直接清空对象原先存活的整个区域,使其成为新的空白区域。
可以看到,”copy and sweep”需要更加复杂的操作,但也让对象可以紧密排列,避免”mark and sweep”中可能出现的空隙。在新建对象时,”copy and sweep”可以提供大块的连续空间。因此,如果对象都比较”长寿”,那么适用于”mark and sweep”。如果对象的”新陈代谢”比较活跃,那么适用于”copy and sweep”。
堆中的内存分配
对中的内存分为:
- 永久世代(permanent generation)中存活的是Class对象,这些对象不会被垃圾回收。
- 成熟世代(tenured generation),成熟世代中的对象世代较久,采用的是mark and sweep回收方法。
- 年轻世代(young generation),年轻世代中的对象世代较近,年轻世代又分为三个区域:
a、一个eden区,新生对象存活于该区域,新生对象指从上次GC后新建的对象。
b、两个survior区,主要用于copy and sweep回收时用。
当新建对象无法放入eden区时,将出发minor collection。JVM采用copy and sweep的策略,将eden区与from区的可到达对象复制到to区。经过一次垃圾回收,eden区和from区清空,to区中则紧密的存放着存活对象。随后,from区成为新的to区, to区成为新的from区。
如果进行minor collection的时候,发现to区放不下,则将部分对象放入成熟世代。另一方面,即使to区没有满,JVM依然会移动世代足够久远的对象到成熟世代。
如果成熟世代放满对象,无法移入新的对象,那么将触发major collection。JVM采用mark and sweep的策略,对成熟世代进行垃圾回收。
内存泄露和溢出
- 内存泄露是指部分对象无法被回收。
- 内存溢出是程序所占用的内存已经达到上限了。
- 内存泄露时内存溢出的原因。
android上的内存泄露
泄露原因
- 资源性对象如cursor、stream等未关闭。
- 非静态内部类会持有外部类的引用,因此非静态内部类的静态实例容易造成内存泄漏。
- handler的不正确使用,如执行延迟任务时。
- fragment或activity中执行耗时任务后,为在destory中取消耗时任务完毕后的回调。
- bitmap没有及时回收
- 注册广播接收器没有反注册。
解决方法
- 代码的正确书写,如关闭流对象、bitmap的回收等
- 在activity或fragment中关闭不必要的回调
- 采用弱引用WeakRefrecence或软引用SoftWeakRefrecence,
参考: