JVM垃圾回收
JVM垃圾回收
- 内存是如何分配和回收的
- 哪些垃圾需要回收
- 什么时候回收
- 如何回收
1. 堆的基本结构
- 对象首先在Eden区域分配
- 在第一次新生代垃圾回收之后
- 如果对象还存活,就会进入s0或者s1
- 对象的年龄还会加1
- 当其年龄增加到15岁的时候,就会晋升到老年代
- 对象晋升到老年代的年龄阈值,可以通过参数
-XX:MaxTenuringThreshold
来进行设置 - 每次GC后,Eden区和From区会被清空,然后to和from会替换
- 即新的To就是上次GC的From
- 当to区被填满的时候,会将所有对象移动到老年代当中
2. 堆的分配策略
2.1 对象优先在eden区分配
- 优先在eden区进行分配
- 如果eden区已经几乎被分配完了,虚拟机就会发起一次minor GC
- 取决于是否survivor区有足够的空间
- 会先放到survivor区
- 或者将新生代的对象提前转移到了老年代当中
- 如果老年代也空间不够,那就会导致full GC的出现了
2.2 大对象直接进入老年代
大对象是需要大量连续内存空间的对象,比如字符串,数组等。 为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
- 分配担保机制
- 一个权衡,是把现在正在处理的大对象放到永久代里面
- 还是说把现在在eden的一些对象放到永久代当中
- https://cloud.tencent.com/developer/article/1082730
2.3 长期存活的对象将进入老年代
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
“Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的 50% 时(默认值是 50%,可以通过 -XX:TargetSurvivorRatio=percent 来设置,参见 issue1199 ),取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”。
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
//survivor_capacity是survivor空间的大小
size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100);
size_t total = 0;
uint age = 1;
while (age < table_size) {
//sizes数组是每个年龄段对象大小
total += sizes[age];
if (total > desired_survivor_size) {
break;
}
age++;
}
uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
...
}
3. 如何判断对象的死亡
3.1 判断对象无效的方法
3.1.1 引用计数法
- 给对象添加引用计数器
- 多一个地方引用它,计数器就加1
- 当引用失效,计数器就减1
- 任何时候计数器为0的对象就是不可能再被使用的
- 主流的虚拟机并没有使用这个算法,因为很难解决对象之间相互循环引用的问题
3.1.2 可达性分析算法
- 从GC Roots对象作为起点开始向下搜索,节点所走过的路径称为引用链
- 当一个对象到GC Roots没有任何引用链相连的话,就证明此对象不可用
- 可以作为GC Roots的对象包括
- 虚拟机栈当中引用的对象
- 本地方法栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 所有被同步锁持有的对象
3.2 引用的类别
3.2.1 强引用 StrongReference
我们当前常使用的基本都属于强引用,如果一个对象具有强引用,那么垃圾回收期绝对不会回收它
3.2.2 软引用 SoftReference
如果内存空间足够,GC不会回收
如果内存空间不足,就会回收这些对象的内存
软引用可以用来实现内存敏感的高速缓存, 软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
3.2.3 弱引用 WeakReference
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
3.2.4 虚引用 PhantomReference
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。
3.3 不可达的类是否必须要回收
要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行
finalize 方法。当对象没有覆盖 finalize 方法,或 finalize
方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
3.4 如何判断一个类是无用的类
类需要同时满足下面 3 个条件才能算是 “无用的类” :
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的
ClassLoader
已经被回收。 - 该类对应的
java.lang.Class
对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
4. 垃圾收集算法
- 标记清除
- 标记复制
- 标记整理
- 分代收集
5. 垃圾收集器
5.1 HotSpot VM GC 分类
- 部分收集 (Partial GC):
- 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
- 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
- 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
- 整堆收集 (Full GC):收集整个 Java 堆和方法区。
5.2 Serial 收集器
- 串行收集器
- 进行垃圾收集的时候工作线程需要停止
- 新生代
- 标记复制
- 老年代
- 标记整理
- 简单高效,适合在client端运转
5.3 ParNew收集器
- Serail收集器的多线程版本
- 新生代
- 标记复制
- 老年代
- 标记整理
- 适合在server模式下
5.4 Parallel Scavenge收集器
- 注重对于吞吐量的优化 — 提高CPU的利用率
- 吞吐量 —> CPU中用于运行用户代码的时间和CPU总消耗时间的比值
- 其提供了很多的参数来实现优化
- 新生代
- 标记复制
- 老年代
- 标记整理
5.5 Serial Old收集器
- Serail收集器的老年代版本
- 两个用途
- JDK1.5之前版本和Parallel Scavenge收集器搭配使用
- 作为CMS收集器的后备方案
5.6 Parallel Old收集器
- Parallel Scavenge的老年版本
- 使用多线程和标记整理法
5.7 CMS收集器
- Concurrent Mark Sweep
- 以获取最短回收停顿时间为目标的收集器
- 非常符合注重用户体验的应用
- 实现了让垃圾收集线程和用户线程的同时工作
- 工作步骤
- 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
- 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
- 优势
- 并发,低停顿
- 缺陷
- 对CPU资源敏感
- 无法处理浮动垃圾
- 标记 清除算法会导致收集结束会产生大量的空间碎片
5.8 G1 收集器
- 面向服务器的垃圾收集器,主要针对多核有大容量内存的机器,以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
- 特征
- 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者
CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1
收集器仍然可以通过并发的方式让 java 程序继续执行。 - 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
- 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
- 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
- 优先列表: 在后台维护一个后台列表,每次根据允许的收集时间,优先选择回收价值最大的region
- 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者
Reference
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 stone2paul@gmail.com
文章标题:JVM垃圾回收
文章字数:2.8k
本文作者:Leilei Chen
发布时间:2021-07-01, 12:40:35
最后更新:2021-07-01, 12:43:43
原始链接:https://www.llchen60.com/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。