gc脑图
如何确定是垃圾
1.引用计数法
缺点:无法解决循环引用的问题
2.可达性分析
- 原理:
选择活动的对象作为GC Roots,然后跟踪引用链条,如果一个对象和GC Roots之间不可达,也就是不存在引用链条。
可作为GC Root的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
可达性分析缺点:
在多线程环境下,其他线程可能会更新已经访问过的对象中的引用,从而造成误报(将引用设置为 null)或者漏报(将引用设置为未被访问过的对象)。
误报并没有什么伤害,Java 虚拟机至多损失了部分垃圾回收的机会。漏报则比较麻烦,因为垃圾回收器可能回收事实上仍被引用的对象内存。一旦从原引用访问已经被回收了的对象,则很有可能会直接导致 Java 虚拟机崩溃。
引用类型是什么
- 出现的理由:
- 当内存空间还足够时,能保留在内存之中。
- 如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。
不同的引用类型,主要体现的是对象不同的可达性状态和对垃圾收集的影响。
类型 | 垃圾回收 | 应用场景 |
---|---|---|
强引用 | 不回收 | |
软引用 | 内存不足时会回收 | 通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。 |
弱引用 | 每次都会回收 | 这就可以用来构建一种没有特定约束的关系,比如,维护一种非强制性的映射关系,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。 |
虚引用 | 回收时会做些事情 | 通常用来做所谓的 Post-Mortem 清理机制,也有人利用幻象引用监控对象的创建和销毁。 |
引用状态流转图
对象何时死亡
在可达性分析中不可达的对象,不一定会被回收。
真正要回收对象至少经历两次标记过程
- 如果对象在可达性分析后没有与GC Roots引用链相连接,就被第一次标记并进行一次筛选,筛选条件是否有必要执行finalize()方法。(1)当对象没有覆盖finalize()方法(2)finalize()方法已经被jvm调用过。如果没有必要就直接回收对象。
- 如果有必要执行finalize(),那么这个对象将会放入到F-Queue的队列中,并由低优先级的Finalizer线程去执行触发。稍后gc将对FQueue中的对象进行第二次标记,如果对象在finalize()中重新与引用链的对象建立关联,就会在第二次标记时它将会获得生存。
|
|
老版本jdk如何回收方法区
主要回收废弃常量和无用的类。
回收废弃常量
假如一个字符串”abc”已经进入了常量池中,但是当前系统没有任何一个String对象是叫做”abc”的,换句话说,就是没有任何String对象引用常量池中的”abc”常量,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个”abc”常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。回收无用的类 必须满足3个条件
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾回收算法
1. 标记-清除(mark-sweep)
- 缺点:
- 造成内存碎片(由于 Java 虚拟机的堆中对象必须是连续分布的,因此可能出现总空闲内存足够,但是无法分配的极端情况。)
- 分配效率低
2. 标记-整理(mark-compact)
标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
3. 复制(copy)
内存区域分为两等分,分别用两个指针 from 和 to 来维护,并且只是用 from 指针指向的内存区域来分配内存。当发生垃圾回收时,便把存活的对象复制到to 指针指向的内存区域中,并且交换 from 指针和 to 指针的内容。复制这种回收方式同样能够
解决内存碎片化的问题。
缺点:
堆空间的使用效率极其低下。
分代收集算法
- 新生代(标记-复制算法)
通过-XX:SurvivorRatio调整Eden和Survivor的大小比例
- Eden
- Survivor(2个分别用from和to指代(to一直是空))
Minor GC:当Eden区的空间耗尽时触发来收集新生代的垃圾,存活下来的对象会被送到Survivor区。
触发时Eden区和from指向的Survivor区中存活对象会被复制到to指向的Survivor区中,然后交换from和to指针。
何时升级到老年代?
- JVM会记录对象在Survivor区一共来回复制几次,如果是15就晋升(-XX:+MaxTenuringThreshold)
- 如果单个Survivor区已经被占用了50%(-XX:TargetSurvivorRatio),较高复制次数的对象也会晋升
- 老年代(标记-整理|标记-清除算法)
遇到的问题:
有些老年代对象可能引用新生代的对象,那么标记该收集哪些类的时候就要连老年代也要去扫描。
HotSpot算法实现
1.枚举根节点
如何实现?
使用一组OopMap的数据结构,在类加载完成的时候,就把对象内什么偏移量是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这样,GC在扫描时就可以直接得知这些信息了。为了解决什么?
每次检查对象存活时必须确保分析过程中对象引用关系在某个冻结的时间点上(Stop the world),并且要逐个从GC Roots节点中检查里面的引用会消耗很多时间。
2.安全点(Safe point)
- 为了解决什么?
在OopMap的协助下,HotSpot会快速且准确地完成GC Roots枚举,可能导致引用关系变化,或者说OopMap内容变化的指令非常多,如果为每一条指令都生成对应的OopMap,那将会需要大量的额外空间,这样GC的空间成本将会变得很高。
安全点的选定:程序“是否具有让程序长时间执行的特征”为标准进行选定的
方法调用、循环跳转、异常跳转等指令才会产生Safepoint
gc发生时让所有执行中的线程到达安全点上后停顿下来
- 抢先式中断(几乎没有jvm会采用这种方法)
在gc发生时,首先把所有线程中断,发现有线程中断的地方不在安全点上,就恢复线程,让它到达安全点。 - 主动式中断
当gc需要中断线程的时候,仅仅简单地设置一个标示,各个线程执行时主动去轮询这个标示,如果发现中断标示为真就自己中断挂起。
3.安全区域(Safe Region)
为了解决什么?
安全点机制保证了程序执行时进入gc的安全点,但程序不执行(没有分配CPU时间)典型的例子就是线程处于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,“走”到安全的地方去中断挂起。安全区域是什么?
指在一段代码片段之中,引用关系不会发生变化,在这个区域中的任意地方开始gc都是安全的工作原理
在线程执行到安全区域中的代码时,首先标识自己已进入了安全区,这段时间里gc发生时,就不去管标识为安全区域状态的线程了。在线程要离开安全区域时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号为止。
读后感笔记来自周志明的《深入理解Java虚拟机(第2版)》