JVM进阶学习

152 阅读9分钟

一 JAVA虚拟机调优的原因和目的

如果CPU使用率较高,GC频繁且GC时间长,可能就需要JVM调优了。

减少java虚拟机发生FULL GC的次数,并且每一次FULL GC的时间要越短越好。

因为发生FULL GC的时候,java虚拟机中所有用户的线程都会被挂起,停止运行。

 

GC垃圾收集行为:分为MINOR GC(小GC)和FULL GC(完全GC)

二 JVM调优基本思路

如果CPU使用率较高,GC频繁且GC时间长,可能就需要JVM调优了。基本思路就是让每一次GC都回收尽可能多的对象。

 对于CMS垃圾收集器来说: 要合理设置年轻代和老年代的大小。该如何确定它们的大小呢?这是一个迭代的过程,可以先采用JVM的默认值,然后通过压测分析GC日志。

  如果看年轻代的内存使用率处在高位,导致频繁的Minor GC,而频繁GC的效率又不高,说明对象没那么快能被回收,这时年轻代可以适当调大一点。

  如果看老年代的内存使用率处在高位,导致频繁的Full GC,这样分两种情况:如果每次Full GC后年老代的内存占用率没有下来,可以怀疑是内存泄漏;如果Full GC后年老代的内存占用率下来了,说明不是内存泄漏,要考虑调大年老代。

  对于G1垃圾收集器来说: 可以适当调大Java堆,因为G1收集器采用了局部区域收集策略,单次垃圾收集的时间可控,可以管理较大的Java堆。

三 在 Java 中,对象什么时候可以被垃圾回收?

判断java对象是否可以回收的方法:

  1. 引用计数法
  2. 可达性分析算法(常用)

当一个对象到GC Roots根不可达时,在下一个垃圾回收周期中尝试回收该对象,如果该对象重写了finalize()方法,并在这个方法中成功自救(将自身赋予某个引用),那么这个对象不会被回收。

  但如果这个对象没有重写finalize()方法或者已经执行过这个方法,也自救失败,该对象将会被回收。

  Java垃圾回收是由 jvm 自动执行的,不是人为操作的,所以当不存在对某对象的任何引用时,该对象就处于被jvm回收的状态,并不是马上予以销毁。

四 JVM的整体架构

 JVM由三个主要的子系统构成

1 类加载器子系统

2 运行时数据区(内存结构)

3 执行引擎

  image.png  

4.2 运行时数据区(内存结构)

image.png  

一个进程会运行一个class文件,进程中有很多的线程,最后运行程序的最小单元是线程

栈:栈里面有栈帧 每一个线程中的每一个方法执行的时候都会在栈中创建一个栈帧

4.3 堆空间结构

image.png

五 垃圾收集算法

image.png

5.1 标记-清除算法

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,效率也很高,但是会带来两个明显的问题:

  •  效率问题
  •  空间问题(标记清除后会产生大量不连续的空间碎片)

image.png

5.2 复制算法

为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

image.png

5.3 标记-整理算法

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

image.png

5.4 分代收集算法

当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

六 垃圾收集器

image.png

6.1 CMS收集器

( -XX:+UseConcMarkSweepGC(old)  -XX:+UseParNewGC)

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

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

整个过程分为四个步骤:

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

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

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

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

image.png

从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿

但是它有下面三个明显的缺点:

  •  对CPU资源敏感(会和服务抢资源);

  •  无法处理浮动垃圾(在java业务程序线程与垃圾收集线程并发执行过程中又产生的垃圾,这种浮动垃圾只能等到下一次gc再清理了);

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

6.2 G1收集器

(-XX:+UseG1GC)

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

image.png

G1将Java堆划分为多个大小相等的独立区域(Region),虽保留新生代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集合。

分配大对象(直接进Humongous区,专门存放短期巨型对象,不用直接进老年代,避免Full GC的大量开销)不会因为无法找到连续空间而提前触发下一次GC。

被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。

它具备以下特点:

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

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

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

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

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

  •  初始标记(initial mark,STW):在此阶段,G1 GC 对根进行标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关。

  •  并发标记(Concurrent Marking):G1 GC 在整个堆中查找可访问的(存活的)对象。

  •  最终标记(Remark,STW):该阶段是 STW 回收,帮助完成标记周期。

  •  筛选回收(Cleanup,STW):筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。

image.png

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

  G1垃圾收集分类

  • YoungGC

 新对象进入Eden区

 存活对象拷贝到Survivor区

 存活时间达到年龄阈值时,对象晋升到Old区

  • MixedGC

 不是FullGC,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)