本文图片均来自:拉勾教育——【Android 工程师进阶 34 讲】
如果不进行垃圾回收,内存耗空是迟早的。因为我们在不断的进行内存分配,而不进行垃圾回收。除非内存足够大,可以让我们随意分配内存。但事实并非如此。
所谓垃圾就是指内存中已经没用的对象。那么我们如何找到这些没用的对象。JVM中使用一种叫做可行性分析的算法来决定对象是否要被回收。
这个算法的思想是通过一系列称为“GCRoot”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GCRoot没有任何引用链(即GCRoots到对象不可达)时,则证明此对象是不可用的。
image
如上图所示,对象A、B、C、D、E与GCRoot之间都存在一条直接或者间接的引用链,这也代表它们与 GC Root之间是可达的,因此它们是不能被GC回收掉的。而对象M和K虽然被对J 引用到,但是并不存在一条引用链连接它们与GCRoot,所以当GC进行垃圾回收时,只要遍历到 J、K、M 这 3 个对象,就会将它们回收。
注意:上图中圆形图标虽然标记的是对象,但实际上代表的是此对象在内存中的引用。包括 GC Root 也是一组引用而并非对象。'
1、虚拟机栈(局部变量)引用的对象 2、方法区中常量引用的对象 3、方法去中静态引用的对象 4、本地方法栈中JNI(Native方法)引用的对象
1、在堆内存分配中,出现可用内存不足导致分配对象内存失败。系统主动GC。 2、在应用层,开发人员执行System.gc()主动执行GC操作。
主要通过如下几种方式进行垃圾回收: 1、标记清除算法 2、复制算法 3、标记压缩算法 4、分代回收算法
标记清除是最基础的GC回收机制,字面意思,标记清除算法分为两步。 标记:找到内存中所有与GCRoot相连/间接性连接的对象标记为灰色(存活对象),否则标记为黑色(垃圾对象)。
清除:将垃圾对象直接清除。
将现有内存分为两块,每次只使用其一,当需要GC的时候,在内存中找到正在使用的对象,复制到另一块内存中,然后将当前内存直接清空。交换2个内存的角色。完成GC。
过程和标记清除类似,但不是直接清除,而是将所有对象移动至内存的一端,然后回收那些不可用的对象。
JVM根据内存对象存活周期,把堆内存划分成了新生代,老年代,和持久代。分代回收的中心思想就是:对于新创建的对象会在新生代中分配内存,此区域的对象生命周期一般较短。如果经过多次回收仍然存活下来,则将它们转移到老年代中。
大批量死去的对象,少量的存活对象,一次GC能回收70%—95%的空间,回收效率极高。一般使用复制算法,复制成本低。
新生代又划分为一个Eden区和两个survivor(存活)区。这三部分按照8:1:1来划分新生代。 绝大多数新建的对象都被放在eden区。如图:
image 当eden区第一次满的时候回进行内存回收。将eden中的垃圾对象进行清除,将存活对象放至S0。此时S1是空的。如图:
image 下一次eden区满的时候,将eden区和S0中所有的垃圾对象清除,将存活对象放至S1,此时S0变为空。如图:
image 如此反复在S0和S1中切换(默认15次),依旧存活的对象放至老年代。
对象存活率高,没有额外空间进行分配,使用标记清除或者标记整理算法。
一个对象在新生代中存在过久没有被清除,此时就会将其移至老年代。老年代的内存空间会比新生代大很多。如果某个对象内存过大,导致无法将其放入新生代时,此对象会直接放入老年代中。
指内存中永久存在的数据,主要指class以及meta(元数据)信息。
判断对象是否存活我们是通过GCRoots的引用可达性来判断的。但是JVM中的引用关系并不止一种,而是有四种,根据引用强度的由强到弱,他们分别是:强引用(StrongReference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。
如果一个对象具有强引用,那么垃圾回收器绝对不会回收它,当内存不足时宁愿抛出 OOM 错误,使得程序异常停止。
Object object = new Object(); 即是一个强引用。
如果一个对象只具有软引用,那么垃圾回收器在内存充足的时候不会回收它,而在内存不足时会回收这些对象。软引用对象被回收后,Java虚拟机会把这个软引用加入到与之关联的引用队列中。
如果一个对象只具有弱引用,那么垃圾回收器在扫描到该对象时,无论内存充足与否,都会回收该对象的内存。与软引用相同,弱引用对象被回收后,Java虚拟机会把这个弱引用加入到与之关联的引用队列中。
虚引用并不决定对象生命周期,如果一个对象只具有虚引用,那么它和没有任何引用一样,任何时候都可能被回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。与软引用和弱引用不同的是,虚引用必须关联一个引用队列。
代码人生,一飞冲天。
END