java 培训:JVM 性能调优理论基础知识分享
JVM 是 Java 虚拟机,它是一个虚构出来的计算机,可在实际的计算机上模拟各种计算机的功能。JVM 有自己完善的硬件结构,例如处理器、堆栈和寄存器等,还具有相应的指令系统。JVM 在整个 JDK 中处于最底层,负责与操作系统的交互。因此掌握 JVM 调优便显得尤为重要。在进行 JVM 调优介绍之前,让我们来简单介绍一下基础知识吧!
1. 什么是垃圾?
在 C 语言中,我们通过 malloc 和 free 来申请和释放内存;在 C++语言中我们通过 new 和 delete 来申请和释放内存。那么 Java 语言当我们 new 了一个对象之后,发生了什么?怎样进行内存的申请和释放的呢?事实上, Java 语言是通过 GC 自动进行内存回收的,java培训从而简化编程,使系统不太容易出错,因为手动释放内存容易造成遗漏回收和重复回收的问题。如果程序中出现了没有任何引用指向的对象,我们将这样的对象称为垃圾。
2. 如何定位垃圾?
定位垃圾一般有 2 种算法,引用计数法和根可达算法。
①引用计数(Reference Count)
引用计数是计算机编程语言中的一种内存管理技术,是指将对象的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。同时引用计数还可以指使用引用计数技术回收未使用资源的垃圾回收算法。
![](https://static001.geekbang.org/infoq/ae/ae3ff267b15fb020f52c20a9d56cec2f.png)
但是引用技术法无法解决循环引用的问题,如下图:
![](https://static001.geekbang.org/infoq/36/36f0a61096d4aec2eeec837e131d9a19.png)
②根可达算法(RootSearching)
根可达算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。GC Roots 包含的元素主要包括:虚拟机栈中引用的对象,本地方法栈 JNI 引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,所有被同步锁 synchronized 持有的对象等。
![](https://static001.geekbang.org/infoq/0a/0a2b2e2a8fc77203a61caff8fa845b52.png)
从上图可以看出有 2 个对象都是 GC Roots,已经用黑色方框圈出,可以看出对象 1,2,3,4,5,6,7 都具有 GC Roots 可达性,也就是存活对象,不能被 GC 回收的对象。而对于对象实例 Garbage 并没有任何一个 GC Roots 与之相连,这便是 GC Roots 不可达的对象,这就是 GC 需要回收的垃圾对象。
3. 常见的垃圾回收算法
①标记清除
标记-清除是一种非常基础和常见的垃圾回收算法。算法分为两个阶段,标记阶段和清除阶段。他们找出所有不可达的对象。并将他们放入空闲列表,这个空闲列表就是垃圾回收的列表,标记阶段标记出所有需要回收的对象,清除阶段回收所有被标记对象所占用的空间。
![](https://static001.geekbang.org/infoq/af/afebc12efe88b02e4cd15ba40c81e837.png)
缺点是:容易产生内存碎片。
②拷贝算法
拷贝算法的出现是为了解决标记-清除算法产生的内存碎片。拷贝算法的思路是:首先将内存分为大小相等的两部分(A,B 两部分),每次只使用其中的一部分(这里假设为 A 区),等 A 区部分用完了,这时候就将这里面还能活下来的对象复制到另一部分内存(这里设为 B 区)中,然后把 A 区中的剩下部分全部清理掉。这样一来每次清理都要对一半的内存进行回收操作,这样内存碎片的问题就解决了,可以说简单,高效。
![](https://static001.geekbang.org/infoq/23/23642c55b1d04ddc8d44327f42937051.png)
③标记压缩
既然叫标记-压缩算法,那么它也分为两个阶段,一个是标记(mark),一个是压缩(compact). 其中标记阶段跟标记-清除算法中的标记阶段是一样的。
![](https://static001.geekbang.org/infoq/24/2422a6f16a0f8255abbac590f186f838.png)
而对于压缩阶段,它的工作就是移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的。
4.JVM 内存分代模型
JVM 不同的垃圾回收器所具有的分代模型有所区别,总体的可以分为逻辑分代和物理分代。除 Epsilon、ZGC、Shenandoah 之外的 GC 均采用逻辑分代模型,包含 Serial、Serial Old、Parallel Scavenge、Parallel Old、ParNew 、CMS、G1 等垃圾回收器,其中,G1 只是逻辑分代但是物理不分代,其他的垃圾回收器既是逻辑分代也是物理分代。
逻辑分代模型如下图所示,其中可分为两代 young 新生代和 old 老年代,java培训机构除了 G1 和 Shenandoah 等垃圾回收期外的其他垃圾回收期中,这两部分内存所占的比例大致为 1:3 的关系,不过这个关系是可改的。新生代主要存放的对象是刚刚 new 出来的对象,老年代存放的对象是垃圾回收已经回收好多次了,但是仍然没有将其回收掉。
新生代中又可分为 eden 伊甸区和两个 survivor 区,内存分配的比例大致是 8:1:1,在垃圾回收期间,将 Eden 区和 survivor1 区/survivor2 区的存活对象一次性复制到 survivor2 区/survivor1 区空间,所以只有 10%的新生代空间是被浪费的,但还有个问题,如果转移复制的时候存活对象超过了 10%,那么将通过分配担保直接将这些对象放入老年代。
![](https://static001.geekbang.org/infoq/42/42f411592287302f57b75165fb8109fc.png)
对象 new 出来之后,首先进行栈上分配,如果分配不下则进入 Eden 区,在 Eden 区经过一次垃圾回收到 servivor1 区,再经过垃圾回收到 servivor2 区,然后经过不断的垃圾回收一直在 servivor1 区和 servivor 区徘徊,最后年龄到了之后,到达 Old 老年代。因为新生代中只有少量对象存活下来,大部分对象都被清理掉了,所以适合采用拷贝算法。因为老年代中大量的对象都存活着,只有少量对象被清理掉,所以适合采用标记清除算法或标记压缩算法。其中在新生代的垃圾回收我们称之为 YGC,其中老年代如果内存满了老年代进行的垃圾回收我们称之为 FGC(Full GC)。
常见的垃圾回收器
自从 JDK 诞生以来,诞生了许多垃圾回收器,诸如 Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1,ZGC,Shenandoah,Eplison 等。
![](https://static001.geekbang.org/infoq/d9/d9b94d3d84cf5a893aa698db0cd29ec6.png)
其中 Epsilon 是一款仅用于调试的垃圾回收器,这里不做过多的讨论。
新生代垃圾回收器主要包括:Serial、ParNew、ParallelScavenge;
老年代垃圾回收器主要包括: SerialOld、CMS、Parallel Old。
G1 只在逻辑上分代,ZGC 和 Shenandoah 不分代。
①Serial & Serial Old:
分别是新生代和老年代的垃圾回收器,串行回收,现在一般不用了。当垃圾回收线程工作的时候,会停止一切工作线程,只进行垃圾回收。这种场景称之为 STW(Stop-The-World)阶段。由于 Serial 收集器进行垃圾回收时的等待时间,即 STW 时间较长,所以只适合在内存空间较小的情况下使用。Serial Old 是老年代的垃圾回收器,采用标记清除或标记压缩算法。
②Parallel Scavenge 和 Parallel Old
因为单线程的效率低,内存空间日益变多,现在常用的是这两种回收器的组合,称为为 PS+PO,这两种在①的基础上增加了多线程。在一定程度上提高了垃圾回收的效率。PO 使用标记压缩算法。这两个组合也是 JDK1.8 的默认垃圾回收器。
③ParNew&CMS
由于 PS+PO 组合还是存在很长时间的 STW,有的甚至长达几个小时或几天,那有没有一种可以与工作线程并发执行的垃圾回收器呢?CMS 便出现了。ParNew 是年轻代的垃圾回收器配合 CMS 进行并行回收,是 Parallel Scavenge 的升级。CMS 的英文全程是 ConcurrentMarkSweep,是在老年代并发的,垃圾回收和应用程序同时运行,以降低 STW 的时间,但是 CMS 同样也存在很多问题,所以现在没有一个版本默认是 CMS,只能手动指定。CMS 既然是 MarkSweep 就会产生很多碎片化问题,碎片产生到一定程度,对象分配不下的时候,就使用 SerialOld 进行老年代回收。
④ G1
G1 是一款面向服务端应用的垃圾回收器。G1 垃圾回收器的大致可划分为以下几个步骤:
初始标记(Initial Marking)--标记一下 GC Roots 能直接关联到的对象。
并发标记(ConcurrentMarking)—从 GC Root 开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。
最终标记(FinalMarking)—为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录。虚拟机将这段时间对象变化记录在线程 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中筛选回收(Live Data Counting and Evacuation)。
G1 相对于 CMS 的优势在于:降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型。
⑤ ZGC & Shenandoah
Shenandoah GC 与 ZGC 同为新一代的低延迟收集器, 使用颜色指针算法,分别由 RedHat 和 Oracle 开发, 目前还在实验阶段, 尚未使用于生产环境。
目前主流 GC 是 G1, 而此两者的延时比 G1 低很多。
6.垃圾回收器和内存大小的关系
Serial 适合于内存较小的机器,一般适合于几十 M,PS 适合于内存上百 M 到几个 G 之间的内存,CMS 适合于 20G 左右的内存,G1 适合于上百 G 左右的内存,ZGC 和 Shenandoah 适合于 4T---16T 的超大内存。
文章来源于沃享荟
评论