《深入理解Java虚拟机》第三章垃圾收集器与内存分配策略(理论部分)

垃圾收集主要针对的是非栈区域(因为栈区域的内存分配和回收具备固定性,于编译期基本已知,随着方法,线程结束就回收了,所以无需过多关注。)

对象的生死

既然是回收垃圾自然是得先判断对象是不是垃圾啊,对象是垃圾的标准就是对象死了,对象是否死亡,有两种判断方式。

  1. 引用计数法:
    当有地方引用到它时,计数器+1,引用失效,计数器-1,若计数器为0时,代表对象死了。不过这种方式会引发循环引用问题(就是虽然没有使用了,不过(y)对象内部引用了某个(x)对象,而那个x内部又引用了y)。
  2. 可达性分析算法:
    通过一系列作为GC ROOT的根对象作为起始节点,根据引用关系向下搜索,若搜不到的便是不可达的。

不过不是说没有被GC ROOT 引用就会立即死亡,它首先会被标记,随后进行筛选,筛选的条件是是否有必要执行finalize()方法,若有,则被放入一个叫做F-Queue的队列中,由虚拟机自动创建的低优先级的Finalizer线程去执行它的方法(但是不一定会等待它执行完,担心出现死循环),而且这些对象会被标记只执行一次,如果在其中该对象被引用了,好了, 这就救活了,如果没有,第二次标记就会将它纳入即将回收的集合中,那就死了。不过这种“拯救”方式及其不提倡因为finalize方法代价高昂,不确定性大,无法确保各个对象的调用顺序。

什么是GC ROOT

JVM中采用第二种方式,可以作为GC-ROOT的对象有:

  1. 虚拟机栈栈帧中的本地变量表引用的对象
  2. 方法区中常量引用的对象
  3. 方法区中静态类属性引用的对象
  4. 本地方法栈中JNI引用对象
  5. JVM中内部引用对象(如基本类型对应的Class对象,一些异常对象,系统类加载器)
  6. 被同步锁持有的对象
  7. 反映JVM中内部情况的JMXBean、JVMTI中注册的回调和本地缓存等。
  8. 或者一些其他加入对象(根据垃圾收集器和区域不同来决定)

主要是一些全局性的引用或者执行上下文(比如虚拟机栈帧中的本地变量表)。

怎么找GC ROOT

知道了GC ROOT是什么,那么该怎么找到它们呢?所有收集器在找到GC ROOT时都需要暂停用户线程,所以不仅需要找到它们还需要高效的找到它们。
目前主流的JVM是准确式垃圾回收,也就说JVM知道某块内存中存放的数据的类型。
HotSpot使用一个叫做OopMap来实现 “准确” ,一旦类加载完成,HotSpot对象内多少偏移量上是什么类型的数据记录下来,在即时编译时也会记录下栈里和寄存器里哪些位置是引用。所以就只需要查询这一部分就可以找到GC ROOT了。

 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

在这里插入图片描述

OopMap存储时机

在程序运行当中,引用也是会随之变化,OopMap不可能每一条指令都存下来吧,这样需要很大的空间的,那么何时存储?
有两个概念一个叫安全点,一个叫安全区域
1.安全点
等待用户线程执行到安全点时才会生成OopMap,也才会暂停线程。
设定安全点的依据是"是否具有让程序长时间运行"为基准,而程序的复用就是满足这个基准的特征(方法调用、循环跳转、异常跳转等)。
那么如果让在垃圾回收时,让用户线程处于最近的安全点呢?
两种方式:
在这里插入图片描述

2.安全区域:
如果有线程处于不执行状态(比如sleep),垃圾收集器不可能一直等待它,这时便引入了安全区域的概念,在该区域内保证引用关系不会发生变化。安全区域可以看作拓展的安全点。(该线程只有收到垃圾回收器依据完成根节点枚举的信号才会离开安全区域)

可被回收的对象

刚才所说的,没有被GC ROOT所引用到的对象才是可回收的,事实上这只针对了强引用,而java1.2之后引用被细分为了四种

  1. 强引用
  2. 软引用:描述有用但是非必须的引用,如果内存回收后,内存仍然不够,在抛出内存溢出异常会对这部分对象进行二次回收(用SoftReference类实现)
  3. 弱引用:非必须对象,只会生存到下次垃圾回收前,下次垃圾回收开始时,无论内存是否够用,都会将其回收掉
  4. 虚引用,无法通过虚引用来取得一个对象实例,它的唯一目的是绑定于某个对象,该对象被回收时可收到一个通知。

对方法区的回收

性价比远不如对堆回收高,因为垃圾少且判断条件严苛。
主要针对废弃的常量不再使用的类型
废弃的常量:JVM中没有使用该常量的地方了
不再使用的方法:

  1. 所有实例被回收
  2. 加载该类的类加载器被回收(JSP重加载)
  3. 该类的.class对象在任何地方都没被引用

可通过参数

-vervose:class
-XX:+ThraceClassLoading
-XX:+ThraceClassLoading 

查看类加载和卸载信息,在大量使用反射、动态代理、CGLib等字节码框架或者频繁自定义类加载器的场景下,就需要JVM具备类型卸载的功能。

垃圾收集算法

垃圾回收分为:引用计数式垃圾回收追踪式垃圾回收,第一种主流JVM未涉及。

分代收集理论

现在的垃圾收集器大多遵守此理论。该理论基于:

  1. 弱分代假说: 大多数对象死得贼快
  2. 强分代假说: 熬过越多次垃圾收集的对象,越难死(简称老不死)

因为容易死的,那就提高回收频率,不容易死的就降低,这就体现了分代的必要性,一般分为老年代和新生代,针对不同的代,采用的清理算法和清理频率肯定就不太一样。
这里也体现了一种局部收集(Partial GC)的思想:

  1. 新生代收集(Minor GC)
  2. 老年代收集(Major GC)
  3. 混合收集(Mixed GC):对整个新生代和部分老年代收集(只有G1收集器有这种行为)

当然也有整堆收集(Full GC):对整个堆和方法区进行收集

  1. 跨代引用假说:
    分代收集自然会引发一个问题,上文说到的GC ROOT,可能位于一个代,而被它引用的对象或许位于另一个代,那这种咋搞,GC ROOT全堆扫描?
    当然不是,JVM中使用一个叫做记忆集的数据结构将某代划分为若干小块,标识某对象的引用位于哪一块,这样GC时,只需扫描那个小块即可。

记忆集: 记录非收集区域指向收集区域指针集合的抽象数据结构
类似这种形式

CARD_TABLE[this address>>9] = flag

由于采用的精度为卡精度,所以只需要将一块内存(2^9=512字节)里是否存在引用标记出来就可以,至于存在多少个(或者说某对象的根引用),就需要扫描了。如果被标识为1说明该内存块存在跨代指针(元素变脏),如果标识为0则说明不存在
记忆集标识时机: 当其他分代对象 引用了 本区域对象时
如何实现: HotSpot虚拟机通过写屏障技术来实现。在赋值前叫写前屏障,赋值后称为写后屏障,生成相应的指令。不过在并发的场景下可能会产生伪共享的性能问题,通过加一个检查来解决,只有卡表元素不脏时才能被标记变脏(可通过-XX:+UseCondCardMark开启检查功能)。

标记-清理算法

最基础的算法:标记存活(或者死亡)对象,标记完成后统一回收。
主要缺点:

  1. 执行效率不稳定:随需要清除对象的增加而增加
  2. 空间碎片化问题
标记-复制算法

将回收空间划分为两块,每次只使用一块,将存活对象复制到另一块,然后再将未复制对象全部回收。
不过有点浪费空间,针对新生代大多数对象熬不过第一轮收集,所以一些新生代收集器将新生代划分为一个Eden区和两块较小的Survivor区(默认为8:1:1), 每次将存活对象,复制到未使用的那块Survivor区域.还有一个逃生门设计,如果Survivor区域不足以容乃存活对象,就将存活的大对象直接挪到老年代。

标记-整理算法

对存活率较高的区域采用的回收算法(复制算法需要复制大量对象,这种场景下效率低下),将存活对象向空间另一端移动,然后直接清理掉边界之外的对象

上面三种方法,第一种是不需要移动对象的算法,后两种种是需要移动对象的算法,移动与否各有利弊:
3. 移动:内存回收时会更加复杂(需要更新GC ROOT中的引用)
4. 不移动:内存分配时会更加复杂(因为产生很多碎片空间,所以当存在大对象时需要用空闲分配链表之类的来解决)
有种混合的方式,就是让虚拟机平时大多时候采用标记清理,到碎片化程度大到影响对象分配时,再用一次标记-整理算法(或者复制)规整一下空间(比如CMS收集器)。

并发场景下的可达性分析

并发标记时对象是否死亡时,可能会产生两种问题

  1. 漏标记

  2. 错标记
    漏标记还是能够忍受的,大不了让那个别漏网之鱼多活一轮嘛,错标记就非常致命了。
    出现错标记需要满足两种条件:

  3. 插入大于等于1条黑色对象对白色对象的引用

  4. 赋值器删除了全部从灰色到新增引用对象的直接和间接引用

初始状态初始状态
扫描过程在这里插入图片描述
正确标记完成在这里插入图片描述
用户线程修改正在扫描的引用,将正在扫描的引用对象赋给被已经标记过的对象在这里插入图片描述
那么针对以上两个条件破坏掉其中一个便可以实现并发标记,

  1. 增量更新(破坏第一个条件): 当黑色对象新增引用白色对象时,记录下新增的引用,等并发扫描结束后以该黑色为根再扫描一遍(相当于黑色对象重新变回灰色对象)(CMS)
  2. 原始快照(破坏2): 将被删除引用的灰色记录下来,并发结束后,再以灰色对象为根再扫描一遍。(G1\Shenandoah)
发布了20 篇原创文章 · 获赞 1 · 访问量 309

猜你喜欢

转载自blog.csdn.net/qq_38732834/article/details/105586603