天天看点

【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现

为什么没有人看我的博客❓
           
【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现

1.垃圾收集器管理范围

1.1 范围

Java堆和方法区。

(原因:这两个区域有很显著的不确定性,这部分内存的分配和回收是动态的)

主要是Java堆。方法区没有要求实现垃圾收集。

1.2 方法区的垃圾收集

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。

废弃的常量:已经没有对象引用常量池中的这个常量,且虚拟机中也没有其他地方引用这个常量。

不再被使用的类(Java虚拟机被允许对满足以下三个条件的无用类进行回收):

1.该类所有实例都已被回收,Java堆中不存在该类及任何派生子类的实例

2.加载该类的类加载器已被回收(这个条件很难达成)

3.该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

2.如何判断对象存活或死亡

2.1 判断算法

2.1.1 引用计数算法

定义:在对象中增加一个引用计数器,有一个地方引用它计数器就增加1;当引用失效时计数器减1。

例外情况举例:对象A和B互相引用,除此之外再无引用。实际上这两个对象已经不可能再被访问,但由于互相引用,计数器不为0,引用计数算法无法回收他们。

2.1.2 可达性分析算法

应用:当前,就是用可达性分析算法来判定对象是否存活的。

思路:通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”。如果某个对象到“GC Roots”没有引用链(或者图论的说法,从GC Roots到这个对象不可达),证明该对象是不可能再被使用的。

【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现

固定(除了固定的,还有其他的)可作为GC Roots的对象包括以下几种:

1.在虚拟机栈(栈帧中的本地变量表)中引用的对象,比如各个线程被调用的方法栈中使用的参数、局部变量、临时变量等。

2.在方法区中静态属性引用的对象,比如Java类的引用类型静态变量。

3.在方法区中常量引用的对象,比如字符串常量池里的引用。

4.在本地方法栈中JNI(通常说的Native方法)引用的对象。

5.Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointException、OutOfMemoryError)等,还有系统类加载器。

6.所有被同步锁(Synchronized关键字)引用的对象。

7.反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

——来自书P71

2.2 Java引用类型

书上这块我不懂,推荐博客https://www.cnblogs.com/dolphin0520/p/3784171.html

强引用:软引用:弱引用:虚引用:

【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现
【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现

2.3 判定不可达不一定会死

要真正宣告一个对象死亡,至少要两次标记。可达性分析算法标记不可达是第一次,第二次标记是对象是否有必要执行finalize()方法。加入对象没有finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。

…具体过程省略…

此外,任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行。

finalize()方法已经是官方不推荐的方法,建议不要使用。

3.垃圾收集算法

3.1 垃圾收集算法的两种类型

【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现

3.2 分代收集理论

3.2.1 定义

分代收集建立在以下两个分代假说:

1.弱分代假说:绝大多数对象都是朝生夕灭的。

2.强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。

这两个分代假说奠定了多款垃圾收集器的一致设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

分代收集理论在商用Java虚拟机中的应用:至少把Java堆划分为新生代和老年代两个区域。

顾名思义,在新生代中,每次垃圾收集都有很多对象死去,而存活下来的对象,将会逐步晋升到老年代存放。

3.2.2 问题:跨代引用

因为存在跨代引用,所以不能很好的分开新生代老年代。因此有了引申的跨代引用假说:

跨代引用相对于同代引用占少数。

面对跨代引用的做法:在新生代上建立一个全局的数据结构(这种数据结构称为“记忆集”)。这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC(新生代收集)时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。

虽然这种方法需要在对象改变引用关系(如将自己或某个属性赋值)时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。

3.3 三种垃圾收集算法

3.3.1 标记-清除算法

过程:

标记所有要清除的对象,标记完后,统一回收掉所有被标记的对象。(也可以反过来标记要保留的)

【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现

缺点:

1.执行效率不稳定。

2.造成大量不连续的内存碎片。空间碎片太多可能会导致当以后在程序过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。

3.3.2 标记-复制算法

3.3.2.1 半区复制

“半区复制”垃圾收集算法:将可用内存划分为两块,每次只使用一块,这块内存使用完了,就将存活的对象复制到另一块上,然后清空这块。

优点:实现简单,运行高效。分配内存时不用考虑空间碎片,只要移动堆顶指针,按顺序分配即可。

缺点:浪费空间。将可用内存缩小为原来的一半。

【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现

3.3.2.2 Appel式回收

“Appel式回收”:把新生代划分为一块较大的Eden空间和两块较小的Survivor。每次使用Eden和一块Survivor。垃圾收集时,把Eden和这块Survivor中还存活的复制到另一块Survivor上,然后清空Eden和用过的那块Survivor。

HotSpot默认Eden和Survivor的大小比例是8:1,即每次新生代中可用内存空间为整个新生代容量的90%。只有另一个Survivor的10%是被浪费的。

【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现

3.3.2.3 标记-复制算法的缺点

1.在对象存活率较高时就要进行较多的复制操作,效率降低。

2.如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况。

所以一般老年代不选用这种算法。

3.3.3 标记-整理算法

过程:

标记过程与“标记-清除”算法一样,但后续步骤是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

【深入理解Java虚拟机】第3章:垃圾收集算法1.垃圾收集器管理范围2.如何判断对象存活或死亡3.垃圾收集算法4.HotSpot算法的细节实现

与标记-清除的区别

“标记-清除”是非移动式的,“标记-整理”是移动式的。

优缺点

优点:避免空间碎片化问题。减少内存分配和访问的时间,提高吞吐量。

缺点:移动必须暂停全部用户程序才能进行(Stop The World)。内存回收时复杂。停顿需要时间。不移动会使收集器效率更高一些。

其他:“和稀泥”

“和稀泥”式的做法:让虚拟机平时多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程序已经大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间。

基于标记-清除算法的CMS收集器面临空间碎片过多时采用的就是这种处理办法。

4.HotSpot算法的细节实现

【深入理解Java虚拟机】第3章:HotSpot对象存活判定算法和垃圾收集算法的细节实现