JVM 垃圾回收


GC 如何判断对象可以被回收
JVM 通过可达性分析算法来判断对象是否存活,从 GC Roots 开始,根据引用关系向下搜索,所经过的路径形成
引用链 Reference Chain, 不在引用链上的对象,到 GC Roots 不可达,视为可回收对象
----------------------------------------------------
什么是 GC Roots?
GC Roots 是一组活跃的引用
主要包括
1. 静态变量
方法区中类静态属性引用的对象
2. 常量
方法区中常量引用的对象
3. 局部变量
虚拟机栈(栈帧中的本地变量表)中引用的对象
4. 本地方法栈中 JNI (native 方法) 中引用的对象
5. java 虚拟机内部的引用,如基本数据类型对应的 class 对象,系统类加载器,常驻异常对象
6. 同步锁 synchronized 持有的对象
----------------------------------------------------
强引用 strongly reference
Object obj = new Object();
只要引用关系还在,垃圾收集器无法回收

软引用 soft reference
系统抛出 OOM 之前,进行二次回收
通过 SoftReference 类实现

弱引用 Weak reference
下一次 GC 回收
通过 WeakReference 类实现, 可用于缓存中的对象

虚引用 phantom reference
又称 幽灵引用 或 幻影引用
无法通过虚引用拿到引用的对象
虚引用被回收时,会收到一个系统通知
通过 PhantomReference 类实现
----------------------------------------------------
方法区类对象的回收规则
1. 该类的所有实例都已经被回收
2. 加载该类的 ClassLoader 已经被回收了
3. 该类的 class 对象没有在代码中被引用
----------------------------------------------------
你知道哪些 GC 类型?
新生代收集 Minor GC / Young GC
针对新生代的垃圾收集

老年代收集 Old GC
针对老年代的垃圾收集

整堆收集 Full GC
对整个 java 堆和方法区的垃圾收集

运行时常量池 垃圾回收
在常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量,常量 "abc" 就是废弃常量,垃圾回收的时候且有必要的话,"abc" 就会被系统清理出常量池

常量池中的类,接口,方法,字段的符号引用与此类似
----------------------------------------------------
标记 - 清除算法 
Mark - Sweep
首先标记所有存活的对象,然后统一回收所有未标记的对象, 如果需要回收的对象很多,效率就会降低, 会产生大量不连续的内存碎片,以后需要分配大对象时, 没有足够大的连续内存, 可能会提前触发一次 GC
----------------------------------------------------
标记 - 复制算法
Mark - Copy
为了解决内存碎片问题, 将可用内存分为大小相等的两块, 每次只使用其中一块, 当使用的这块空间不够用了, 就将存活对象复制到另一块, 再把已使用过的内存空间一次清理掉, 主要用于新生代
实现简单, 运行高效, 解决了内存碎片问题, 代价是可用内存会变小, 要留一部分空间来倒腾存活对象

HotSpot 虚拟机把新生代划分为一块较大的 Eden 和两块较小的 Survivor, 每次分配内存只使用 Eden 和其中一块 Survivor, 垃圾收集时将 Eden 和 Survivor 中仍然存活的对象一次性复制到另一块 Survivor 上, 然后直接清理掉 Eden 和已用过的那块 Survivor
 
HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例是 8:1, 即新生代可用内存空间为整个新生代容量的 90%

如果另一块 Survivor 不够空间存放上一次新生代收集下来的存活对象, 这些对象就通过分配担保机制直接进入老年代
----------------------------------------------------
标记 - 整理算法 
Mark - Compact
一般用于老年代,首先标记所有存活的对象,然后把所有存活对象都移向内存空间一端, 最后清理掉边界以外的内存
----------------------------------------------------
OopMap - ordinary object pointer Map
安全点 - safe point
HotSpot 虚拟机在指令流的特定位置生成 OopMap, 记录栈中引用类型数据的位置, 
垃圾收集之前, 必须让用户线程在指令流里 OopMap 的位置停下来, 这些位置就叫 安全点

虚拟机需要暂停用户线程, 就设置一个标志位, 用户线程会轮询这个标志位, 看是否需要中断挂起

安全区域 Safe Region
某一段代码片段中,引用关系不会发生变化,用户线程在这段代码片段里时, 并不妨碍虚拟机垃圾回收,所以叫安全区域

用户线程执行到安全区域里的代码时, 会标识自己进入安全区域, 
当用户线程要离开安全区域时, 会检查虚拟机是否还处于需要暂停用户线程的阶段, 看情况决定是否等待

记忆集
是一种用于记录从非收集区域指向收集区域的指针集合的抽象数据结构

卡表
卡表是一个字节数组, 每一个元素都对应着一个特定大小的内存块, 叫卡页, 卡表用来记录哪些卡页里的对象含有跨代引用, 垃圾收集时, 就把它们加入 GC Roots 中一并扫描

写屏障
hotSpot 虚拟机通过写屏障来维护卡表状态, 写屏障可以看作是在虚拟机层面对 引用类型字段赋值 的 AOP 切面, 虚拟机会为所有赋值操作生成相应的指令, 更新卡表
----------------------------------------------------
三色标记算法
白色 未扫描,或已扫描不可达
黑色 已扫描,且安全存活
灰色 正在扫描

三色标记算法缺陷
并发标记阶段,有可能会产生多标或者漏标

多标
用户线程去掉了黑色到灰色的引用

漏标 - 需同时满足2个条件
1.用户线程去掉了灰色到白色的引用
原始快照
可以通过写屏障记录下来要删除的引用,等并发扫描结束之后,再将这些记录过的引用关系扫描一次,就好像按照删除前的对象图快照扫描

2.用户线程增加了黑色到白色的引用
增量更新
可以通过写屏障记录下来黑到白的引用,等并发扫描结束之后,再将这些记录过的引用关系扫描一次

hotSpot 虚拟机中, CMS 基于增量更新做并发标记, G1, Shenandoah 则是用原始快照来实现
----------------------------------------------------
并行 parallel
在谈垃圾收集器的时候,并行就是 有多条垃圾收集器线程协同工作,而用户线程处于等待状态

并发 concurrent
在谈垃圾收集器的时候,并发就是 垃圾收集器线程与用户线程同时工作
----------------------------------------------------
serial 收集器
新生代,标记复制算法,单线程收集器
客户端模式下默认的新生代收集器,适合小内存环境
----------------------------------------------------
serial old 收集器
老年代,标记整理算法,单线程收集器
可用于客户端模式下,也可用于服务器模式下 CMS 收集器收集垃圾报错时候的后备方案
----------------------------------------------------
parnew 收集器
新生代,标记复制算法,多线程并行收集,多 CPU 环境 Server 模式与 CMS 配合使用
jdk 7 之前服务端模式下的首选新生代收集器
----------------------------------------------------
parallel scavenge 收集器
标记复制算法,并行收集器
适合注重吞吐量,如没有太多交互的后台运算
----------------------------------------------------
parallel old 收集器
老年代,标记整理算法,并行收集器
适合注重吞吐量,如没有太多交互的后台运算
----------------------------------------------------
CMS 问题很多,jdk 1.8 和后面的版本都没有把它设置成默认的垃圾回收器
jdk 9 不推荐使用, jdk 14 remove 掉

jdk 1.8 里 CMS 有 83 个参数
java -XX:+PrintFlagsFinal|grep CMS

jdk 1.8 里 G1 有 24 个参数
java -XX:+PrintFlagsFinal|grep G1
----------------------------------------------------
CMS 收集器 - Concurrent mark sweep
老年代,标记清除算法,并发收集器
目标是获取最短垃圾回收停顿时间,适合关注服务响应时间的互联网应用

整个过程分为以下4个步骤
1.初始标记
只是标记一下 GC Roots 能直接关联的对象, 速度很快, 需要暂停用户线程

2.并发标记
从 GC Roots 直接关联的对象开始遍历整个对象图的过程, 和用户线程一起工作

3.重新标记
通过 增量更新 来处理并发标记阶段, 因用户线程继续运行而产生的漏标, 需要暂停用户线程

4.并发清除
清除 GC Roots 不可达对象, 和用户线程一起工作

由于耗时最长的 并发标记 和 并发清除 阶段, 垃圾收集线程和用户线程并发工作,  所以总体上来看 CMS 收集器的内存回收 和 用户线程 是一起并发执行

分代设计
90% 的对象朝生夕灭
----------------------------------------------------
内存分配回收策略
对象优先在 Eden 区分配
对象在新生代 Eden 区分配, 当 Eden 区不够空间分配时, 触发 Minor GC, Eden 区里的存活对象, 会进入 Survior 区, 如果 Survior 区放不下, 通过分配担保机制提前转移到老年代

大对象直接进入老年代
为了避免大对象在 Eden 区和两个 Survior 区之间来回复制, 产生大量的内存复制动作, 
Serial 和 ParNew 两款新生代收集器提供了一个参数配置, 大于该参数的对象直接在老年代分配
-XX: PretenureSizeThreshold

长期存活的对象将进入老年代
对象在 Eden 区诞生, 熬过一次 Minor GC 后仍然存活, 且能被 Survior 容纳的话, 该对象被转移至 Survior 区, 对象头里的 Age 变成 1, 以后在 Survior 区每熬过一次 Minor GC, 年龄就增加一岁, 当年龄达到阈值(默认15), 就晋升到老年代
-XX:MaxTenuringThreshold

动态对象年龄判定
当同年对象大小总和大于 survivor 空间一半,可以直接进入老年代 
----------------------------------------------------
Minor GC 还是 Full GC
在 Minor GC 之前, 虚拟机会判断, 老年代的连续可用空间是否大于 新生代对象的总大小, 
或者历次晋升的平均大小, 如果大于就 Minor GC, 否则就 Full GC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我三师弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值