写点什么

GitHub 标星过万!亦直问 JVM(1)

发布于: 2021 年 08 月 05 日

适合场景:


  • 存活对象少 比较高效

  • 扫描了整个空间(标记存活对象并复制异动)

  • 适合年轻代


缺点:


  • 需要空闲空间

  • 需要复制移动对象


6.5.4 标记整理算法

根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。



6.6 有哪些常见的垃圾回收器?简单介绍一下



6.6.1 CMS

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。


CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。


从名字中的 Mark Sweep 这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:


  1. 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快;

  2. 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。

  3. 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。

  4. 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。



从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:


  1. 对 CPU 资源敏感

  2. 无法处理浮动垃圾

  3. 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生

6.6.2 G1

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。


被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备以下特点:


  1. 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。

  2. 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。

  3. 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。

  4. 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。



G1 收集器的运作大致分为以下几个步骤:


  1. 初始标记:stw 从 gc root 开始直接可达的对象

  2. 并发标记:gc root 对对象进行可达性分析 找出存活对象(可达性分析算法)

  3. 最终标记

  4. 筛选回收:根据用户期待的 gc 停顿时间指定回收计划


G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。


回收模式:


1. young gc


Young GC 主要是对 Eden 区进行 GC,它在 Eden 空间耗尽时会被触发。在这种情况下,Eden 空间的数据移动到 Survivor 空间中,如果 Survivor 空间不够,Eden 空间的部分数据会直接晋升到年老代空间。Survivor 区的数据移动到新的 Survivor 区中,也有部分数据晋升到老年代空间中。最终 Eden 空间的数据为空,GC 停止工作,应用线程继续执行。


2. mixed gc


Mix GC 不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。


它的 GC 步骤分 2 步:



1. 全局并发标记(global concurrent marking)
2. 拷贝存活对象(evacuation)
复制代码

6.6.3 CMS 和 G1 的区别

  1. G1 分区域 每个区域是有老年代概念的,但是 CMS 以整个区域为单位收集

  2. G1 回收后马上合并空闲内存,CMS 在 STW 的时候做

6.6.4 内存区域设置

  1. XX:G1HeapRegionSize: 设置 G1 收集器一个 Region 的大小。取值范围从 1M 到 32M,且是 2 的指数。如果不设定,那么 G1 会根据 Heap 大小自动决定。

  2. 复制成活对象到一个区域,暂停所有线程


6.7 什么是三色标记算法?




提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有用的方法,利用它可以推演回收器的正确性。 首先,我们将对象分成三种类型的。


  • 黑色:根对象,或者该对象与它的子对象都被扫描

  • 灰色:对象本身被扫描,但还没扫描完该对象中的子对象

  • 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象


当 GC 开始扫描对象时,按照如下图步骤进行对象的扫描:


  1. 根对象被置为黑色,子对象被置为灰色。



  1. 继续由灰色遍历,将已扫描了子对象的对象置为黑色。



  1. 遍历了所有可达的对象后,所有可达的对象都变成了黑色。不可达的对象即为白色,需要被清理。



写入屏障(在进行并发标记时若有对象的增删):


当黑色对象直接引用了一个白色对象后,我们就将这个黑色对象记录下来,在扫描完成后,重新对这个黑色对象扫描,这个就是增量更新(Incremental Update)


当删除了灰色对象到白色对象的直接或间接引用后,就将这个灰色对象记录下来,再以此灰色对象为根,重新扫描一次。这个就是原始快照(Snapshot At TheBeginning,SATB)


6.8 什么时候会触发 Full GC?



6.8.1 System.gc()

System.gc()方法的调用。此方法的调用是建议 JVM 进行 Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加 Full GC 的频率,也即增加了间歇性停顿的次数。建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC 来禁止 RMI(Java 远程方法调用)调用 System.gc()。

6.8.2 老年代写满

老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行 Full GC 后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space 。为避免以上两种状况引起的 FullGC,调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

6.8.3 持久代空间不足

Permanet Generation 中存放的为一些 class 的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation 可能会被占满,在未配置为采用 CMS GC 的情况下会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出错误信息:java.lang.OutOfMemoryError: PermGen space 。为避免 Perm Gen 占满造成 Full GC 现象,可采用的方法为增大 Perm Gen 空间或转为使用 CMS GC。


6.9 Minor GC、Major GC 和 Full GC 之间的区别




  1. Minor GC: 从年轻代空间(包括 Eden 和 Survivor 区域) 回收内存被称为 Minor GC。

  2. Major GC: Major GC 是清理老年代

  3. Full GC: Full GC 是清理整个堆空间—包括年轻代和老年代


6.9 什么时 STW?如何避免 STW?




Java 中 Stop-The-World 机制简称 STW,是在执行垃圾收集算法时,Java 应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java 中一种全局暂停现象,全局停顿,所有 Java 代码停止,native 代码可以执行,但不能与 JVM 交互;这些现象多半是由于 gc 引起。所以要尽量避免 Full GC。


6.10 JVM 实战调优经验



6.10.1 性能调优

  1. 设置堆的最大最小值 -xms -xmx

  2. -Xms:为 jvm 启动时分配的初始堆的大小,也是堆大小的最小值,比如-Xms200m,表示分配 200M

  3. -Xmx:为 jvm 运行过程中分配的最大堆内存,比如-Xmx500m,表示 jvm 进程最多只能够占用 500M 内存

  4. -Xss:为 jvm 启动的每个线程分配的内存大小,默认 JDK1.4 中是 256K,JDK1.5+中是 1M

  5. 调整老年和年轻代的比例

  6. 默认的,新生代 ( Young )与老年代 (Old)的比例的值为 1:2(该值可以通过参数 –XX:NewRatio 来指定)

  7. 默认的,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。

  8. -XX:newSize:新生代初始化内存的大小(注意:该值需要小于-Xms 的值)。防止年轻代堆收缩:老年代同理。

  9. 年轻代和年老代设置多大参考:

  10. 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通 GC 的周期,但会增加每次 GC 的时间,小的年老代会导致更频繁的 Full GC。

  11. 更小的年轻代必然导致更大年老代,小的年轻代会导致普通 GC 很频繁,但每次的 GC 时间会更短,大的年老代会减少 Full GC 的频率。

  12. 如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性。


在抉择时应该根据以下两点:


  1. 本着 Full GC 尽量少的原则,让年老代尽量缓存常用对象,JVM 的默认比例 1:2 也是这个道理。

  2. 通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响 Full GC 的前提下,根据实际情况加大年轻代,比如可以把比例控制在 1:1。但应该给年老代至少预留 1/3 的增长空间。

  3. 主要看是否存在更多持久对象和临时对象

  4. 观察一段时间 看峰值老年代如何 不影响 gc 就加大年轻代

  5. 配置好的机器可以用 并发收集算法:-XX:+UseParallelOldGC

  6. 每个线程默认会开启 1M 的堆栈 存放栈帧 调用参数 局部变量 太大了 500k 够了


7.原则 就是减少 GC STW

6.10.2 FullGC 内存泄露排查

  1. 监控配置 自动 dump


#出现 OOME 时生成堆 dump:



-XX:+HeapDumpOnOutOfMemoryError
复制代码


#生成堆文件地址:



-XX:HeapDumpPath=/home/liuke/jvmlogs/
复制代码

6.10.3 OOM 种类

  1. 堆 OOM

  2. 解决方法: 增大 Xmx 值 使用 MAT,JProfile 等工具进行代码分析与优化

  3. 直接内存 OOM

  4. 解决方法: 增大 MaxDirectMemorySize 的值

  5. 过多线程导致的 OOM

  6. 解决方法: 减少线程数、或减少堆空间与线程空间

  7. 永久区(Pern)溢出

  8. 运行结果: OOM Permgen sace 解决方法:

  9. 增大 MaxPremSize 的值

  10. 减少系统需要的类的数量

  11. 使用 ClassLoader 合理装载类,并进行回收

  12. GC 效率低下引起的 OOM

  13. 判断 GC 引起 OOM 的条件

  14. 花在 GC 上的时间是否超过了 98%

  15. 老年代释放的内存是否小于 2%

  16. Eden 区释放的内存是否小于 2%

  17. 是否连续 5 次 GC 都出现了上述几种情况


关闭 OOM 可以通过参数:-XX:-UseGCOverheadLimit 进行设置

6.10.4 逃逸分析

逃逸分析:指的是虚拟机在运行期通过计算分析将原本在堆上分配的对象改成在栈中分配,这样的好处是栈上分配的对象随着线程的结束而自动销毁,不依赖于 GC,可以降低垃圾收集器运行的频率。


如何判定为逃逸?


JVM 判断新创建的对象是否逃逸的依据有两个:


1. 对象被赋值给堆中对象的字段和类的静态变量


2. 对象被传进了不确定的代码中去运行


如果满足了以上情况的任意一种,那这个对象 JVM 就会判定为逃逸。

6.10.5 可达性

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象

  2. 方法区中类静态属性引用的对象

  3. 方法区中常量变量引用的对象

  4. 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

  5. 活跃线程的引用对象

6.10.6 JVM 何时需要调优?

JVM 调优目标:使用较小的内存占用来获得较高的吞吐量或者较低的延迟。


  1. OOM

  2. 内存泄漏

  3. 线程死锁

  4. 锁争用

  5. Java 进程消耗 CPU 过高

6.10.7 JVM 性能检测工具

  1. Jconsole

  2. Jprofiler

  3. jvisualvm

  4. MAT

6.10.7 内存泄漏

不再会被使用的对象的内存不能被回收,就是内存泄露。


  1. 生产机 dump

  2. Mat

  3. Jmap

最后

金三银四马上就到了,希望大家能好好学习一下这些技术点,需要领取这些学习资料和面试笔记的朋友请**赶紧点击这里免费获取!**


学习视频:



大厂面试真题:



用户头像

VX:vip204888 领取资料 2021.07.29 加入

还未添加个人简介

评论

发布
暂无评论
GitHub标星过万!亦直问JVM(1)