关于java:我所知道JVM虚拟机之垃圾回收器详细篇

8次阅读

共计 21689 个字符,预计需要花费 55 分钟才能阅读完成。

前言

上篇文章对垃圾回收器进行了概述解说,那么本篇开始将具体理解垃圾回收器

一、GC 的分类和性能指标


垃圾回收器概述

咱们说垃圾收集器没有在标准中进行过多的规定,能够由不同的厂商、不同版本的 JVM 来实现

因为 JDK 的版本处于高速迭代过程中,因而 Java 倒退至今曾经衍生了泛滥的 GC 版本

咱们从不同角度剖析垃圾收集器,能够将 GC 分为不同的类型

Java 不同版本新个性

语法层面:Lambda 表达式、switch、主动拆箱装箱、enum、泛型

API 层面:Stream API、新的日期工夫、Optional、String、汇合框架

底层优化:JVM 优化、GC 的变动、元空间、动态域、字符串常量池等

垃圾回收器分类

按线程数分(垃圾回收线程数),能够分为串行垃圾回收器和并行垃圾回收器

串行回收指的是在同一时间段内只容许有一个 CPU 用于执行垃圾回收操作,将此时工作线程被暂停直至垃圾收集工作完结

在诸如单 CPU 处理器或者较小的利用内存等硬件平台不是特地优越的场合,串行回收器的性能体现能够超过并行回收器和并发回收器。所以 串行回收默认被利用在客户端的 Client 模式下的 JVM 中

在并发能力比拟强的 CPU 上,并行回收器产生的进展工夫要短于串行回收器

和串行回收相同,并行收集能够 使用多个 CPU 同时执行垃圾回收 ,因而晋升了 利用的吞吐量不过并行回收依然与串行回收一样,采纳独占式 应用了“Stop-the-World”机制

依照工作模式分,能够分为并发式垃圾回收器和独占式垃圾回收器

并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的进展工夫

独占式垃圾回收器(Stop the World)一旦运行,就进行应用程序中的所有用户线程,直到垃圾回收过程齐全完结

按碎片解决形式分,可分为压缩式垃圾回收器和非压缩式垃圾回收器

  • 压缩式垃圾回收器会在回收实现后,对存活对象进行压缩整顿打消回收后的碎片

    * 再调配对象空间:` 应用指针碰撞 `
  • 非压缩式的垃圾回收器不进行这步操作

    * 调配对象空间:` 应用闲暇列表 `
    

按工作的内存区间分,又可分为年老代垃圾回收器和老年代垃圾回收器

评估 GC 的性能指标

================================

吞吐量:

运行用户代码的工夫占总运行工夫的比例(总运行工夫 = 程序的运行工夫 + 内存回收的工夫)

垃圾收集开销:

吞吐量的补数,垃圾收集所用工夫与总运行工夫的比例

暂停工夫:

执行垃圾收集时,程序的工作线程被暂停的工夫越小越好

收集频率:

绝对于应用程序的执行,收集操作产生的频率

内存占用:

Java 堆区所占的内存大小

疾速:

一个对象从诞生到被回收所经验的工夫,不必的时候尽量回收掉

咱们把吞吐量、暂停工夫、内存占用这三者独特形成一个“不可能三角”

三者总体的体现会随着技术提高而越来越好。一款优良的收集器通常最多同时满足其中的两项

这三项里 暂停工夫的重要性日益凸显。因为随着硬件倒退,内存占用多些越来越能容忍硬件性能的晋升也有助于升高收集器运行时对应用程序的影响 即进步了吞吐量。而内存的扩充对提早反而带来负面成果

简略来说次要抓住两点:吞吐量、暂停工夫

评估 GC 的性能指标:吞吐量

================================

吞吐量就是 CPU 用于运行用户代码的工夫与 CPU 总耗费工夫的比值

即吞吐量 = 运行用户代码工夫 /(运行用户代码工夫 + 垃圾收集工夫)

比方:虚拟机总共运行了 100 分钟,其中垃圾收集花掉 1 分钟那吞吐量就是 99%

这种状况下应用程序能容忍较高的暂停工夫,因而 高吞吐量的应用程序有更长的工夫基准,反而疾速响应是不用思考的

评估 GC 的性能指标:暂停工夫

================================

“暂停工夫”是指一个时间段内应用程序线程暂停,让 GC 线程执行的状态

例如:GC 期间 100 毫秒的暂停工夫意味着 在这 100 毫秒期间内没有应用程序线程是流动的

评估 GC 的性能指标:吞吐量 VS 暂停工夫

================================

高吞吐量较好 因为这会让应用程序的最终用户感觉只有应用程序线程在做“生产性”工作。直觉上吞吐量越高程序运行越快

从最终用户的角度来看 低暂停工夫(低提早)较好,然而不论是 GC 还是其余起因导致一个利用被挂起始终是不好的。

这取决于应用程序的类型,有时候甚至短暂的 200 毫秒暂停都可能打断终端用户体验 。因而具备 较低的暂停工夫是十分重要的,特地是对于一个交互式应用程序(就是和用户交互比拟多的场景)

可怜的是”高吞吐量”和”低暂停工夫”是一对相互竞争的指标(矛盾)

  • 因为如果抉择以吞吐量优先,那么 必然须要升高内存回收的执行频率,然而这样会导致 GC 须要更长的暂停工夫来执行内存回收
  • 相同的,如果抉择以低提早优先为准则,那么为了升高每次执行内存回收时的暂停工夫,也只能频繁地执行内存回收,但这又引起了年老代内存的缩减和导致程序吞吐量的降落

在设计(或应用)GC 算法时,咱们必须确定咱们的指标:

  • 一个 GC 算法只可能针对两个指标之一(即只专一于较大吞吐量或最小暂停工夫)
  • 或者尝试找到一个二者的折衷

当初规范:在最大吞吐量优先的状况下,升高进展工夫

二、不同的垃圾回收器的概述


垃圾收集机制是 Java 的招牌能力,极大地提高了开发效率。这当然也是面试的热点

咱们能够用一张图来概括一些垃圾回收器的发展史

那么,Java 常见的垃圾收集器有哪些?咱们接下来介绍七种经典的垃圾收集器

  • 串行回收器:Serial、Serial old
  • 并行回收器:ParNew、Parallel Scavenge、Parallel old
  • 并发回收器:CMS、G1

7 款经典回收器与垃圾分代之间的关系

================================

新生代收集器:Serial、ParNew、Parallel Scavenge

老年代收集器:Serial old、Parallel old、CMS

整堆收集器:G1

垃圾收集器的组合关系

================================

两个收集器间有连线,表明它们能够搭配应用,咱们依据虚线与实线进行解说解说

JDK8 前(不蕴含 JDK8)

年老代:Serial GC 可搭配老年代 CMS GC、Serial Old GC

年老代:ParNew GC 可搭配老年代 CMS GC、Serial Old GC

年老代:Parallel Scavenge GC 可搭配老年代 Parallel Old GC、Serial Old GC

其中 Serial Old 作为 CMS 呈现”Concurrent Mode Failure”失败的后备预案

在 JDK 8 的时候

因为保护和兼容性测试的老本,将(红色虚线)Serial + CMS、ParNew + Serial Old 这两个组合申明为废除(JEP173),并在 JDK9 中齐全勾销了这些组合的反对(JEP214)即:移除

在 JDK 14 的时候

将弃用(绿色虚线)Parallel Scavenge 和 Serial Old GC 组合(JEP366)

将删除(青色虚线)CMS 垃圾回收器(JEP363)

那么咱们下面提到七种垃圾回收器,那么为什么须要那么多呢?一个不够吗?

因为 Java 的应用场景很多,挪动端,服务器等。所以就须要针对不同的场景,提供不同的垃圾收集器,进步垃圾收集的性能

尽管咱们会对各个收集器进行比拟,但并非为了筛选一个最好的收集器进去。没有一种放之四海皆准、任何场景下都实用的完满收集器存在,更加没有万能的收集器。所以 咱们抉择的只是对具体利用最合适的收集器

查看默认的垃圾回收器

================================

常见的形式有

  • -XX:+PrintCommandLineFlags:查看命令行相干参数(蕴含应用的垃圾收集器)
  • 应用命令行指令:jinfo -flag 相干垃圾回收器参数 过程 ID

咱们应用一个示例代码来领会领会看这两种形式查看默认的回收期

public class GcUseTest {public static void main( string[] args) {ArrayList<byte[]> list = new ArrayList<>();
        while(true){byte[] arr = new byte[100];
            list.add(arr);
            try {Thread.sleep( millis: 10);
            }catch (InterruptedException e) {e.printstackTrace();
            }    
        }
    }
}

咱们应用第一种形式设置相干的 JDK 8 环境与相干参数

这时咱们运行起程序,看看控制台输入的默认垃圾回收器

接下来咱们应用第二种形式采纳命令行的形式查看一下

接下来咱们更换环境,采纳 JDK9 的环境应用参数看看是什么

这时咱们运行起程序,看看控制台输入的默认垃圾回收器

接下来咱们应用第二种形式采纳命令行的形式查看一下

三、Serial 回收器:串行回收


Serial 收集器是最根本、历史最悠久的垃圾收集器了。JDK1.3 之前回收新生代惟一的抉择

Serial 收集器作为 HotSpot 中 Client 模式下的默认新生代垃圾收集器

Serial 收集器 采纳复制算法、串行回收 "Stop-the-World" 机制 的形式执行内存回收

除了年老代之外 Serial 收集器还提供用于执行老年代垃圾收集的 Serial Old 收集器,Serial old 收集器同样也采纳了串行回收和”Stop the World”机制,只不过内存回收算法应用的是标记 - 压缩算法

Serial Old 是运行在 Client 模式下默认的老年代的垃圾回收器

Serial Old 在 Server 模式下次要有两个用处:

  • ①与新生代的 Parallel Scavenge 配合应用
  • ②作为老年代 CMS 收集器的后备垃圾收集计划

这个收集器是一个单线程的收集器,“单线程”的意义:它只会应用一个 CPU(串行)或一条收集线程去实现垃圾收集工作 。更重要的是在它 进行垃圾收集时,必须暂停其余所有的工作线程,直到它收集完结(Stop The World)

在 HotSpot 虚拟机中,应用 -XX:+UseSerialGC 参数能够指定年老代和老年代都应用串行收集器

接下来应用示例代码来领会设置该 Serial GC

public class GCUseTest {public static void main(string[] args){ArrayList<byte[]> list = new ArrayList<>();
            while(true){byte[] arr = new byte[100];
            list.add(arr);
            try {Thread.sleep( millis: 10);
            }catch (InterruptedException e) {e.printstackTrace() ;
            }
        }
    }
}

首先咱们该程序选项里配置参数应用 Serial GC 看看

这时咱们运行起程序,看看设置参数后是否以后为 Serial 回收器

咱们也能够依据下面查看默认回收器那样,应用 cmd 命令查看

Serial 回收器的劣势

================================

简略而高效(与其余收集器的单线程比):

对于限定单个 CPU 的环境来说,Serial 收集器因为没有线程交互的开销,分心做垃圾收集天然能够取得最高的单线程收集效率。运行在 Client 模式下的虚拟机是个不错的抉择

在用户的桌面利用场景中,可用内存个别不大(几十 MB 至一两百 MB),能够在较短时间内实现垃圾收集(几十 ms 至一百多 ms),只有不频繁产生,应用串行回收器是能够承受的

不过对于交互较强的利用而言这种垃圾收集器是不能承受的。个别在 Java Web 应用程序中是不会采纳串行垃圾收集器的

四、ParNew 回收器:并行回收


Serial GC 是年老代中的单线程垃圾收集器,那么 ParNew 收集器则是 Serial 收集器的多线程版本

  • Par 是 Parallel 的缩写,New:只能解决新生代

ParNew 收集器除了采纳 并行回收 的形式执行内存回收外,两款垃圾收集器之间简直没有任何区别。ParNew 收集器在年老代中同样也是采纳复制算法、”Stop-the-World” 机制

ParNew 是很多 JVM 运行在 Server 模式下新生代的默认垃圾收集器

  • 对于新生代,回收次数频繁,应用并行形式高效。
  • 对于老年代,回收次数少,应用串行形式节俭资源。

那么因为 ParNew 收集器基于并行回收是否能够判定 ParNew 收集器的回收效率在任何场景下都会比 Serial 收集器更高效?

其实不然,ParNew 收集器 运行在多 CPU 的环境下,因为能够充分利用多 CPU、多外围等物理硬件资源劣势,能够更疾速地实现垃圾收集,晋升程序的吞吐量

然而在单个 CPU 的环境下,ParNew 收集器不比 Serial 收集器更高效。

尽管 Serial 收集器是基于串行回收,然而因为 CPU 不须要频繁地做工作切换,因而能够无效防止多线程交互过程中产生的一些额定开销。除 Serial 外,目前只有 ParNew GC 能与 CMS 收集器配合工作

在程序中能够通过选项 "-XX:+UseParNewGC" 手动指定应用 ParNew 收集器执行内存回收工作。它示意年老代应用并行收集器,不影响老年代

-XX:ParallelGCThreads:限度线程数量,默认开启和 CPU 数据雷同的线程数

接下来咱们应用下面的示例代码来设置对应的 ParNew 回收器,先通过选项来设置

后果当咱们运行起来的时候发现,程序呈现谬误

这时因为在 JDK9 中齐全勾销了(红色虚线)Serial + CMS、ParNew + Serial Old 这些组合的反对

此时咱们将环境更换为 JDK8 的环境

此时咱们当程序运行起来就能够看到还是能够应用的

五、Paralle 回收器:吞吐量优先


HotSpot 的年老代中除了领有 ParNew 收集器是基于并行回收的以外,Parallel Scavenge 收集器同样也采纳了复制算法、并行回收和”Stop the World”机制

那么 Parallel 收集器的呈现是否多此一举?
  • 其实不然和 ParNew 收集器不同,Parallel Scavenge 收集器的指标则是达到一个 可管制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器
  • 以及自适应调节策略也是 Parallel Scavenge 与 ParNew 一个重要区别。(动静调整内存分配情况,以达到一个最优的吞吐量或低提早)

高吞吐量则能够高效率地利用 CPU 工夫,尽快实现程序的运算工作,次要适宜在后盾运算而不须要太多交互的工作 。因而常见在服务器环境中应用。例如, 那些执行批量解决、订单解决、工资领取、科学计算的应用程序

在 Parallel Old 收集器没进去之前应用的是串行的 Serial Old 收集器,而服务器端硬件比拟高应用并行的形式相比串行性情更高,但老年代还是应用串行的形式所显得达不到吞吐量最大的成果

所以 Parallel 收集器在 JDK1.6 时提供了用于执行老年代垃圾收集的 Parallel Old 收集器,用来代替老年代的 Serial Old 收集器,并且也是基于并行回收和”Stop-the-World”机制

在程序吞吐量优先的利用场景中,Parallel 收集器和 Parallel Old 收集器的组合,在 server 模式下的内存回收性能很不错

并且在 在 Java8 中,默认是此垃圾收集器,咱们能够运行程序一起看看

Parallel 的回收器参数设置

================================

  • -XX:+UseParallelGC:手动指定年老代应用 Parallel 并行收集器执行内存回收工作
  • -XX:+UseParallelOldGC:手动指定老年代都是应用并行回收收集器

下面两个参数别离实用于新生代和老年代。默认开启一个,另一个也会被开启(相互激活)

  • -XX:ParallelGCThreads:设置年老代并行收集器的线程数

个别地最好与 CPU 数量相等,以防止过多的线程数影响垃圾收集性能,在默认状况下当 CPU 数量小于 8 个,ParallelGCThreads 的值等于 CPU 数量

当 CPU 数量大于 8 个,ParallelGCThreads 的值等于 3 +[5*CPU_Count]/8]

  • -XX:MaxGCPauseMillis:设置垃圾收集器最大进展工夫(即 STW 的工夫)。单位是毫秒

为了尽可能地把进展工夫管制在设置工夫以内,收集器在工作时会调整 Java 堆大小或者其余一些参数

对于用户来讲进展工夫越短体验越好。然而在服务器端,咱们重视高并发,整体的吞吐量。所以服务器端适宜 Parallel,进行管制(MaxGCPauseMillis 该参数应用需谨慎)

  • XX:+UseAdaptiveSizePolicy:设置 Parallel Scavenge 收集器具备 自适应调节策略

开启后在该模式下,年老代的大小、Eden 和 Survivor 的比例、降职老年代的对象年龄等参数会被主动调整,已达到在堆大小、吞吐量和进展工夫之间的平衡点

六、CMS 回收器:低提早


在 JDK1.5 期间,Hotspot 推出了一款在 强交互利用中(就是和用户打交道的援用)简直可认为有划时代意义的垃圾收集器:CMS(Concurrent-Mark-Sweep)收集器

这款收集器是 HotSpot 虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作 ,它的关注点是 尽可能缩短垃圾收集时用户线程的进展工夫

进展工夫越短(低提早)就越适宜与用户交互的程序,良好的响应速度能晋升用户体验

目前很大一部分的 Java 利用集中 在互联网站或者 B / S 零碎的服务端上,这类利用尤其器重服务的响应速度,心愿零碎进展工夫最短,以给用户带来较好的体验。CMS 收集器就十分合乎这类利用的需要

CMS 的垃圾收集算法采纳标记 - 革除算法,并且也会”Stop-the-World”

可怜的是 CMS 作为老年代的收集器,却无奈与 JDK1.4.0 中曾经存在的新生代收集器 Parallel Scavenge 配合工作(因为实现的框架不一样,没方法兼容应用),所以在 JDK1.5 中应用 CMS 来收集老年代的时候,新生代只能抉择 ParNew 或者 Serial 收集器中的一个

在 G1 呈现之前,CMS 应用还是十分宽泛的。始终到明天,依然有很多零碎应用 CMS GC

CMS 工作原理(过程)

================================

CMS 整个过程比之前的收集器要简单整个过程分为 4 个次要阶段:

即初始标记阶段 (波及 STW)、并发标记阶段、从新标记阶段(波及 STW) 和并发革除阶段

初始标记(Initial-Mark)阶段:

在这个阶段中,程序中所有的工作线程都将会因为“Stop-the-World”机制而呈现短暂的暂停,这个阶段的次要工作仅仅只是标记出 GC Roots 能间接关联到的对象

一旦标记实现之后就会复原之前被暂停的所有利用线程。因为间接关联对象比拟小,所以 速度十分快

并发标记(Concurrent-Mark)阶段:

从 GC Roots 的间接关联对象开始遍历整个对象图的过程,这个过程耗时较长然而 不须要进展用户线程 能够与垃圾收集线程一起并发运行

从新标记(Remark)阶段:

因为在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者穿插运行,修改并发标记期间,因用户程序持续运作而导致标记产生变动的那一部分对象的标记记录

这个阶段的进展工夫通常会比初始标记阶段稍长一些,并且也会导致“Stop-the-World”的产生,但也远比并发标记阶段的工夫短

并发革除(Concurrent-Sweep)阶段:

此阶段 清理删除掉标记阶段判断的曾经死亡的对象,开释内存空间 因为不须要挪动存活对象,所以这个阶段也是能够与用户线程同时并发的

只管 CMS 收集器采纳的是并发回收(非独占式),然而在其初始化标记和再次标记这两个阶段中依然须要执行“Stop-the-World”机制 暂停程序中的工作线程,不过暂停工夫并不会太长。

因而能够阐明目前所有的垃圾收集器都做不到齐全不须要“Stop-the-World”,只是 尽可能地缩短暂停工夫 因为最消耗工夫的并发标记与并发革除阶段都不须要暂停工作,所以整体的回收是低进展的

另外因为 在垃圾收集阶段用户线程没有中断,所以在 CMS 回收过程中还应该确保应用程序用户线程有足够的内存可用 因而 CMS 收集器不能像其余收集器那样等到老年代简直齐全被填满了再进行收集。

而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在 CMS 工作过程中仍然有足够的空间反对利用程序运行

要是 CMS 运行期间预留的内存无奈满足程序须要,就会呈现一次 "Concurrent Mode Failure" 失败,这时虚拟机将启动后备预案: 长期启用 Serial old 收集器来从新进行老年代的垃圾收集,这样进展工夫就很长了

留神:CMS 收集器的垃圾收集算法采纳的是 标记革除算法,这意味着每次执行完内存回收后,因为被执行内存回收的无用对象所占用的内存空间极有可能是不间断的一些内存块。

不可避免地将会产生一些内存碎片 。那么 CMS 在为新对象分配内存空间时,将无奈应用指针碰撞(Bump the Pointer)技术,而只可能 抉择闲暇列表(Free List)执行内存调配

那么为什么 CMS 不采纳标记 - 压缩算法呢?

因为当并发革除的时候,用 Compact 整顿内存的话原来的用户线程应用的内存还怎么用呢?要保障用户线程能继续执行,前提的它运行的资源不受影响嘛。

Mark Compact 更适宜“stop the world”这种场景下应用

CMS 的长处 与 弊病

================================

长处:

并发收集、低提早

弊病:

会产生内存碎片:导致并发革除后,用户线程可用的空间有余。在无奈调配大对象的状况下,不得不提前触发 Full GC

CMS 收集器对 CPU 资源十分敏感:在并发阶段,它尽管不会导致用户进展,然而会因为占用了一部分线程而导致应用程序变慢,总吞吐量会升高

CMS 收集器无奈解决浮动垃圾 :即 在并发标记阶段如果产生新的垃圾对象,CMS 将无奈对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,可能呈现 ”Concurrent Mode Failure” 失败而导致另一次 Full GC 的产生

CMS 参数配置

================================

-XX:+UseConcMarkSweepGC

手动指定应用 CMS 收集器执行内存回收工作,开启该参数后会主动将 -XX:+UseParNewGC 关上。即:ParNew(Young 区)+CMS(Old 区)+Serial Old(Old 区备选计划)的组合

-XX:CMSInitiatingOccupanyFraction:

设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。JDK5 及以前版本的默认值为 68,即当老年代的空间使用率达到 68% 时,会执行一次 CMS 回收。JDK6 及以上版本默认值为 92%

如果内存增长迟缓,则能够设置一个稍大的值,大的阀值能够无效升高 CMS 的触发频率,缩小老年代回收的次数能够较为显著地改善应用程序性能

反之如果应用程序内存使用率增长很快,则应该升高这个阈值,以防止频繁触发老年代串行收集器。因而通过该选项便能够无效升高 Full GC 的执行次数

-XX:+UseCMSCompactAtFullCollection:

用于指定在执行完 Full GC 后对内存空间进行压缩整顿,以此防止内存碎片的产生。不过因为内存压缩整顿过程无奈并发执行,所带来的问题就是进展工夫变得更长了

-XX:CMSFullGCsBeforeCompaction:

设置在执行多少次 Full GC 后对内存空间进行压缩整顿

-XX:ParallelCMSThreads:

用于设置 CMS 的线程数量,CMS 默认启动的线程数是 (ParallelGCThreads + 3) / 4,ParallelGCThreads 是年老代并行收集器的线程数,能够当做是 CPU 最大反对的线程数。当 CPU 资源比拟缓和时,受到 CMS 收集器线程的影响,应用程序的性能在垃圾回收阶段可能会十分蹩脚

那么 Serial GC、Parallel GC、Concurrent Mark Sweep GC 这三个 GC 有什么不同呢?
  • 如果你想要最小化地应用内存和并行开销,请选 Serial GC;
  • 如果你想要最大化应用程序的吞吐量,请选 Parallel GC;
  • 如果你想要最小化 GC 的中断或进展工夫,请选 CMS GC

JDK 后续版本中 CMS 的变动

================================

JDK9 新个性:CMS 被标记为 Deprecate 了(JEP291)

如果对 JDK9 及以上版本的 HotSpot 虚拟机应用参数 -XX:+UseConcMarkSweepGC 来开启 CMS 收集器的话,用户会收到一个正告信息,提醒 CMS 将来将会被废除

JDK14 新个性:删除 CMS 垃圾回收器(JEP363)移除了 CMS 垃圾收集器

如果在 JDK14 中应用 XX:+UseConcMarkSweepGC 的话,JVM 不会报错,只是给出一个 warning 信息,然而不会 exit。JVM 会主动回退以默认 GC 形式启动 JVM

七、G1 回收器:区域化分代式


后面咱们提到了几个 GC,那么为什么 为什么还要公布 Garbage First(G1)GC?

起因就在于应用程序所 应答的业务越来越宏大、简单,用户越来越多 ,没有 GC 就不能保障应用程序失常进行,而 常常造成 STW 的 GC 又跟不上理论的需要,所以才会一直地尝试对 GC 进行优化

G1(Garbage-First)垃圾回收器是在 Java7 update4 之后引入的一个新的垃圾回收器,是当今收集器技术倒退的最前沿成绩之一

与此同时,为了适应当初不断扩大的内存和一直减少的处理器数量 ,进一步升高暂停工夫(pause time),同时兼顾良好的吞吐量。官网给 G1 设定的指标是 在提早可控的状况下取得尽可能高的吞吐量,所以才担当起“全功能收集器”的重任与冀望

为什么名字叫 Garbage First(G1)呢?

================================

因为 G1 是一个并行回收器,它把堆内存宰割为很多不相干的区域(Region)(物理上不间断的)。应用不同的 Region 来示意 Eden、幸存者 0 区,幸存者 1 区,老年代等

G1 GC 有打算地防止在整个 Java 堆中进行全区域的垃圾收集,G1 跟踪各个 Region 外面的垃圾沉积的价值大小(回收所取得的空间大小以及回收所需工夫的经验值),在后盾保护一个优先列表,每次依据容许的收集工夫,优先回收价值最大的 Region

因为这种形式的侧重点在于回收垃圾最大量的区间(Region),所以取:垃圾优先(Garbage First)

G1 的区域分代化

================================

G1(Garbage-First)是一款 面向服务端利用的垃圾收集器 ,次要 针对装备多核 CPU 及大容量内存的机器,以极高概率满足 GC 进展工夫的同时,还兼具高吞吐量的性能特色

在 JDK1.7 版本正式启用,移除了 Experimental 的标识,是 JDK9 当前的默认垃圾回收器,取代了 CMS 回收器以及 Parallel+Parallel Old 组合。被 Oracle 官网称为“全功能的垃圾收集器”

与此同时,CMS 曾经在 JDK9 中被标记为废除(deprecated)。

G1 在 JDK8 中还不是默认的垃圾回收器 ,须要应用-XX:+UseG1GC 参数来启用

G1 垃圾回收器的特点(劣势)

================================

与其余 GC 收集器相比,G1 应用了全新的分区算法,其特点如下所示:

并行与并发兼备

并行性:G1 在回收期间,能够有多个 GC 线程同时工作,无效利用多核计算能力。此时用户线程 STW

并发性:G1 领有与应用程序交替执行的能力,局部工作能够和应用程序同时执行,因而一般来说,不会在整个回收阶段产生齐全阻塞应用程序的状况

分代收集

从分代上看 G1仍然属于分代型垃圾回收器,它会辨别年老代和老年代,年老代仍然有 Eden 区和 Survivor 区。但从堆的构造上看,它不要求整个 Eden 区、年老代或者老年代都是间断的,也不再保持固定大小和固定数量,简而言之就是如下图所示

而将堆空间分为若干个区域(Region),这些区域中蕴含了逻辑上的年老代和老年代

与各类回收器不同 G1 它同时兼顾年老代和老年代

空间整合

CMS 回收器是:“标记 - 革除”算法、内存碎片、若干次 GC 后进行一次碎片整顿

G1 回收器是:将内存划分为一个个的 region。内存的回收是以 region 作为根本单位的。

Region 之间是复制算法,但整体上理论可看作是标记 - 压缩(Mark-Compact)算法,两种算法都能够防止内存碎片。这种个性有利于程序长时间运行,调配大对象时不会因为无奈找到间断内存空间而提前触发下一次 GC。尤其是当 Java 堆十分大的时候,G1 的劣势更加显著

可预测的进展工夫模型(即:软实时 soft real-time)

这是 G1 绝对于 CMS 的另一大劣势,G1 除了谋求低进展外,还能 建设可预测的进展工夫模型,能让使用者明确指定在一个长度为 M 毫秒的工夫片段内,耗费在垃圾收集上的工夫不得超过 N 毫秒

因为分区的起因,G1 能够只选取局部区域进行内存回收,这样放大了回收的范畴因而对于全局进展状况的产生也能失去较好的管制。

并且 G1 跟踪各个 Region 外面的垃圾沉积的价值大小(回收所取得的空间大小以及回收所需工夫的经验值),在后盾保护一个优先列表,每次依据容许的收集工夫,优先回收价值最大的 Region。保障了 G1 收集器在无限的工夫内能够获取尽可能高的收集效率

相比于 CMS GC,G1 未必能做到 CMS 在最好状况下的延时进展,然而最差状况要好很多

G1 回收器的毛病

================================

相较于 CMS,G1 还不具备全方位、压倒性劣势。比方在用户程序运行过程中,G1 无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额定执行负载(overload)都要比 CMS 要高

从教训上来说,在小内存利用上 CMS 的体现大概率会优于 G1,而 G1 在大内存利用上则施展其劣势。平衡点在 6 -8GB 之间

常见 G1 参数设置

================================

-XX:+UseG1GC:

手动指定应用 G1 垃圾收集器执行内存回收工作

-XX:G1HeapRegionSize:

设置每个 Region 的大小。值是 2 的幂,范畴是 1MB 到 32MB 之间,指标是依据最小的 Java 堆大小划分出约 2048 个区域。默认是堆内存的 1 /2000

-XX:MaxGCPauseMillis:

设置冀望达到的最大 GC 进展工夫指标,JVM 会尽力实现,但不保障达到。默认值是 200ms

-XX:+ParallelGCThread:

设置 STW 时 GC 线程数的值。最多设置为 8

-XX:ConcGCThreads:

设置并发标记的线程数。将 n 设置为并行垃圾回收线程数(ParallelGcThreads)的 1 / 4 左右

-XX:InitiatingHeapOccupancyPercent:

设置触发并发 GC 周期的 Java 堆占用率阈值。超过此值,就触发 GC。默认值是 45

G 1 收集器的常见操作步骤

================================

G1 的设计准则就是简化 JVM 性能调优,开发人员只须要简略的三步即可实现调优:

  • 第一步:开启 G1 垃圾收集器
  • 第二步:设置堆的最大内存
  • 第三步:设置最大的进展工夫

G1 中提供了三种垃圾回收模式:YoungGC、Mixed GC 和 Full GC,在不同的条件下被触发。

G 1 的实用场景

================================

面向服务端利用,针对具备大内存、多处理器的机器。(在一般大小的堆里体现并不惊喜)

最次要的利用是须要低 GC 提早,并具备大堆的应用程序提供解决方案

如:在堆大小约 6GB 或更大时,可预测的暂停工夫能够低于 0.5 秒;(G1 通过每次只清理一部分而不是全副的 Region 的增量式清理来保障每次 GC 进展工夫不会过长)

在上面的状况时,应用 G1 可能比 CMS 好:

  • 超过 50% 的 Java 堆被流动数据占用;
  • 对象调配频率或年代晋升频率变化很大;
  • GC 进展工夫过长(长于 0.5 至 1 秒)

HotSpot 垃圾收集器里,其余的垃圾收集器均应用内置的 JVM 线程执行 GC 的多线程操作,而 G1 GC 能够采纳利用线程承当后盾运行的 GC 工作,即当 JVM 的 GC 线程处理速度慢时,零碎会调用应用程序线程帮忙减速垃圾回收过程

分区 Region:化整为零

================================

应用 G1 收集器时,它将整个 Java 堆划分成约 2048 个大小雷同的独立 Region 块.

每个 Region 块大小依据堆空间的理论大小而定,整体被管制在 1MB 到 32MB 之间,且为 2 的 N 次幂,即 1MB,2MB,4MB,8MB,16MB,32MB。能够通过

能够应用 XX:G1HeapRegionSize 设定。所有的 Region 大小雷同,且在 JVM 生命周期内不会被扭转

尽管还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分 Region(不须要间断)的汇合。通过 Region 的动态分配形式实现逻辑上的间断

接下来咱们对场景进行一个简略的介绍,请看如下图

一个 Region 有可能属于 Eden,Survivor 或者 Old/Tenured 内存区域。然而一个 Region 只可能属于一个角色。图中的 E 示意该 Region 属于 Eden 内存区域,S 示意属于 Survivor 内存区域,O 示意属于 Old 内存区域。图中空白的示意未应用的内存空间

G1 垃圾收集器还减少了一种新的内存区域,叫做 Humongous 内存区域,如图中的 H 块。这区域旨在包容规范区域大小的 50%或更大的对象。,它们存储为一组间断区域。最初一种区域类型是堆的未应用区域

那么为什么要设置 H 区域呢?

================================

对于堆中的大对象,默认间接会被调配到老年代,然而如果 它是一个短期存在的大对象 就会对垃圾收集器造成负面影响。

为了解决这个问题,G1 划分了一个 Humongous 区,它用来专门寄存大对象。如 果一个 H 区装不下一个大对象,那么 G1 会寻找间断的 H 区来存储

为了能找到间断的 H 区有时候不得不启动 Full GC。G1 的大多数行为都把 H 区作为老年代的一部分来对待

Regio 的细节

================================

每个 Region 都是通过指针碰撞来调配空间

G1 为每一个 Region 设计了两个名为 TAMS(Top at Mark Start)的指针把 Region 中的一部分空间划分进去用于并发回收过程中的新对象调配,并发回收时新调配的对象地址都必须要在这两个指针地位以上

TLAB 还是用来保障并发性

G1 垃圾回收流程

================================

G1 GC 的垃圾回收过程次要包含如下三个环节:

  • 年老代 GC(Young GC)
  • 老年代并发标记过程(Concurrent Marking)
  • 混合回收(Mixed GC)
有可能呈现第四个环节:

如果须要,单线程、独占式、高强度的 Full GC 还是持续存在的。它针对 GC 的评估失败提供了一种失败爱护机制,即强力回收

顺时针:Young GC –> Young GC+Concurrent Marking –> Mixed GC 程序,进行垃圾回收

G1 垃圾回收器器回收流程

================================

应用程序分配内存,当年老代的 Eden 区用尽时开始年老代回收过程

G1 的 年老代收集阶段是一个并行的独占式收集器,在年老代回收期 G1 GC 暂停所有应用程序线程,启动多线程执行年老代回收。

而后 从年老代区间挪动存活对象到 Survivor 区间或者老年区间,也有可能是两个区间都会波及

当堆内存应用达到肯定值(默认 45%)时,开始老年代并发标记过程

标记实现马上开始混合回收过程,对于一个混合回收期。

G1 GC从老年区间挪动存活对象到闲暇区间,这些闲暇区间也就成为了老年代的一部分。

和年老代不同,老年代的 G1 回收器和其余 GC 不同,G1 的老年代回收器不须要整个老年代被回收,一次只须要扫描 / 回收一小部分老年代的 Region 就能够了。同时这个老年代 Region 是和年老代一起被回收的

举个例子:一个 Web 服务器,Java 过程最大堆内存为 4G,每分钟响应 1500 个申请,每 45 秒钟会新调配大概 2G 的内存。G1 会每 45 秒钟进行一次年老代回收,每 31 个小时整个堆的使用率会达到 45%,会开始老年代并发标记过程,标记实现后开始四到五次的混合回收

G1 垃圾回收器器回收流程:Remembered Set(记忆集)

================================

咱们说一个对象会被不同区域(年老区、老年区)援用的问题,当咱们判断是否可达的时候,须要各遍历一遍年老区与老年区,是否援用了这个对象。

所以咱们接下来介绍一下:Remembered Set(记忆集)

咱们说一个 Region 不可能是孤立的,一个 Region 中的对象可能被其余任意 Region 中对象援用,判断对象存活时,那么咱们是否须要扫描整个 Java 堆能力保障精确?

其实在其余的分代收集器,也存在这样的问题(而 G1 更突出,因为 G1 次要针对大堆)

若咱们回收新生代也不得不同时扫描老年代?这样的话会升高 Minor GC 的效率

所以咱们有以下的形式解决这个问题

  • 无论 G1 还是其余分代收集器,JVM 都是应用 Remembered Set 来防止全堆扫描
  • 每个 Region 都有一个对应的 Remembered Set
  • 每次 Reference 类型数据写操作时,都会产生一个 Write Barrier 临时中断操作

这时查看将要写入的援用指向的对象是否和该 Reference 类型数据在不同的 Region(其余收集器:查看老年代对象是否援用了新生代对象)

若不同通过 CardTable 把相干援用信息记录到援用指向对象的所在 Region 对应的 Remembered Set 中

当进行垃圾收集时,在 GC 根节点的枚举范畴退出 Remembered Set

这时咱们就能够保障不进行全局扫描,也不会有脱漏,也不须要扫描整个 Java 堆能力保障精确了

G1 回收过程一:年老代 GC

================================

JVM 启动时 G1 先筹备好 Eden 区,程序在运行过程中一直创建对象到 Eden 区,当 Eden 空间耗尽时,G1 会启动一次年老代垃圾回收过程(年老代回收只回收 Eden 区和 Survivor 区)

YGC 时首先 G1 进行应用程序的执行(Stop-The-World),G1 创立回收集(Collection Set),回收集是指须要被回收的内存分段的汇合,年老代回收过程的回收集蕴含年老代 Eden 区和 Survivor 区所有的内存分段

这时请看咱们回收前的图与回收后的图

咱们将回收前的 E 区和 S 区,回收后将残余存活的对象会复制到新的 S 区(达到阈值能够晋升为 O 区)

第一阶段:扫描根

根是指 static 变量指向的对象,正在执行的办法调用链条上的局部变量等,根援用连同 RSet 记录的内部援用作为扫描存活对象的入口

第二阶段:更新 RSet

解决 dirty card queue 中的 card 更新 RSet。此阶段实现后,RSet 能够精确的反映老年代对所在的内存分段中对象的援用。

那么对于 dirty card queue 咱们有以下的阐明

对于应用程序的援用赋值语句 object feldl = objedt, jVM 会在代码执行之前和之后进行非凡的操作指在 dity card queue 中入队一个保留了对象援用信息的 card

在年老代回收的时候, G1 会对 Dirty Card Queue 中所有的 card 进行解决,以更新 RSet,保障 RSet 实时精确的反映正确的援用关系

那为什么不在援用赋值语句处间接更新 RSet 呢? 这是为了性能的须要,RSet 的解决须要线程同步,开销会很大,应用队列性能会好很多。

第三阶段:解决 RSet

辨认被老年代对象指向的 Eden 中的对象,这些被指向的 Eden 中的对象被认为是存活的对象

第四阶段:复制对象

此阶段:对象树被遍历,Eden 区内存段中存活的对象会被复制到 Survivor 区中空的内存分段,Survivor 区内存段中存活的对象

如果年龄未达阈值,年龄会加 1,达到阀值会被会被复制到 Old 区中空的内存分段

如果 Survivor 空间不够,Eden 空间的局部数据会间接降职到老年代空间

第五阶段:解决援用

解决 Soft,Weak,Phantom,Final,JNI Weak 等援用。最终 Eden 空间的数据为空,GC 进行工作,而指标内存中的对象都是间断存储的,没有碎片,所以复制过程能够达到内存整理的成果,缩小碎片

G1 回收过程二:并发标记过程

================================

第一阶段:初始标记阶段

标记从根节点间接可达的对象。这个阶段是 STW 的,并且会触发一次年老代 GC。正是因为该阶段时 STW 的,所以咱们只扫描根节点可达的对象,以节省时间

第二阶段:根区域扫描(Root Region Scanning)

G1 GC 扫描 Survivor 区间接可达的老年代区域对象,并标记被援用的对象。这一过程必须在 Young GC 之前实现,因为 Young GC 会应用复制算法对 Survivor 区进行 GC

第三阶段:并发标记(Concurrent Marking)

在整个堆中进行并发标记(和应用程序并发执行),此过程可能被 Young GC 中断

在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立刻回收

同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例占多少)

第四阶段:再次标记(Remark)

因为应用程序继续进行,须要修改上一次的标记后果,这时是 STW 的。G1 中采纳了比 CMS 更快的原始快照算法:Snapshot-At-The-Beginning(SATB)

第五阶段:独占清理(cleanup,STW)

计算各个区域的存活对象和 GC 回收比例,并进行排序,辨认能够混合回收的区域。为下阶段做铺垫。是 STW 的。(这个阶段并不会实际上去做垃圾的收集)

第六阶段:并发清理阶段

辨认并清理齐全闲暇的区域

G1 回收过程三:混合回收过程

================================

当越来越多的对象降职到老年代 Old Region 时,为了防止堆内存被耗尽,虚构机会触发一个混合的垃圾收集器,即 Mixed GC。

该算法并不是一个 Old GC,除了回收整个 Young Region,还会回收一部分的 Old Region

这里须要留神:是一部分老年代,而不是全副老年代。能够抉择哪些 Old Region 进行收集,从而能够对垃圾回收的耗时工夫进行管制。也要留神的是 Mixed GC 并不是 Full GC

并发标记完结当前老年代中百分百为垃圾的内存分段被回收了,局部为垃圾的内存分段被计算了进去。

默认状况下这些老年代的内存分段会分 8 次(能够通过 -XX:G1MixedGCCountTarget 设置)被回收。【意思就是一个 Region 会被分为 8 个内存段】

混合回收的回收集(Collection Set)包含八分之一的老年代内存分段,Eden 区内存分段,Survivor 区内存分段。

混合回收的算法和年老代回收的算法齐全一样,只是回收集多了老年代的内存分段。具体过程请参考下面的年老代回收过程

因为老年代中的内存分段默认分 8 次回收,G1 会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收

并且有一个阈值会决定内存分段是否被回收。XX:G1MixedGCLiveThresholdPercent,默认为 65%

意思是垃圾占内存分段比例要达到 65% 才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会破费更多的工夫

混合回收也并不一定要进行 8 次。有一个阈值-XX:G1HeapWastePercent,默认值为 10%,意思是容许整个堆内存中有 10% 的空间被节约,意味着如果发现能够回收的垃圾占堆内存的比例低于 10%,则不再进行混合回收。因为 GC 会破费很多的工夫然而回收到的内存却很少

G1 回收可选的过程四:Full GC

================================

G1 的初衷就是要防止 Full GC 的呈现,然而如果上述形式不能失常工作,G1 会进行应用程序的执行(Stop-The-World),应用 单线程 的内存回收算法进行垃圾回收,性能会十分差,应用程序进展工夫会很长

要防止 Full GC 的产生,一旦产生 Full GC,须要对 JVM 参数进行调整。什么时候会产生 Ful1GC 呢?

比方堆内存太小,当 G1 在复制存活对象的时候没有空的内存分段可用,则会回退到 Full GC,这种状况能够通过增大内存解决

导致 G1 Full GC 的起因可能有两个:

  • EVacuation 的时候没有足够的 to-space 来寄存降职的对象
  • 并发处理过程实现之前空间耗尽

G1 补充

================================

从 Oracle 官网走漏进去的信息可获知,回收阶段(Evacuation)其实本也有想过设计成与用户程序一起并发执行,但这件事件做起来比较复杂,思考到 G1 只是回一部分 Region,进展工夫是用户可管制的,所以并不迫切去实现。

而抉择把这个个性放到了 G1 之后呈现的低提早垃圾收集器(即 ZGC)中 ,另外还思考到 G1 不是仅仅面向低提早, 进展用户线程可能最大幅度提高垃圾收集效率,为了保障吞吐量 所以才抉择了齐全暂停用户线程的实现计划

G1 回收器的优化倡议

================================

年老代大小:

防止应用 -Xmn 或 -XX:NewRatio 等相干选项显式设置年老代大小,因为固定年老代的大小会笼罩可预测的暂停工夫指标。咱们让 G1 本人去调整

暂停工夫指标不要太过严苛:

G1 GC 的吞吐量指标是 90% 的应用程序工夫和 10% 的垃圾回收工夫

评估 G1 GC 的吞吐量时,暂停工夫指标不要太严苛。

指标太过严苛示意你违心接受更多的垃圾回收开销,而这些会间接影响到吞吐量

八、垃圾回收器总结


7 种垃圾回收器的比拟

================================

截止 JDK1.8,一共有 7 款不同的垃圾收集器。每一款的垃圾收集器都有不同的特点

在具体应用的时候,须要依据具体的状况选用不同的垃圾收集器

怎么抉择垃圾回收器

================================

Java 垃圾收集器的配置对于 JVM 优化来说是一个很重要的抉择,抉择适合的垃圾收集器能够让 JVM 的性能有一个很大的晋升。怎么抉择垃圾收集器?

  1. 优先调整堆的大小让 JVM 自适应实现。
  2. 如果内存小于 100M,应用串行收集器
  3. 如果是单核、单机程序,并且没有进展工夫的要求,串行收集器
  4. 如果是多 CPU、须要高吞吐量、容许进展工夫超过 1 秒,抉择并行或者 JVM 本人抉择
  5. 如果是多 CPU、谋求低进展工夫,需疾速响应(比方提早不能超过 1 秒,如互联网利用),应用并发收集器
  6. 官网举荐 G1,性能高。当初互联网的我的项目,根本都是应用 G1。

最初须要明确一个观点:

  1. 没有最好的收集器,更没有万能的收集算法
  2. 调优永远是针对特定场景、特定需要,不存在一劳永逸的收集器

九、GC 日志剖析


罕用参数配置

================================

通过浏览 GC 日志,咱们能够理解 Java 虚拟机内存调配与回收策略。

内存调配与垃圾回收的参数列表

  1. -XX:+PrintGC:输入 GC 日志。相似:-verbose:gc
  2. -XX:+PrintGCDetails:输入 GC 的具体日志
  3. -XX:+PrintGCTimestamps:输入 GC 的工夫戳(以基准工夫的模式)
  4. -XX:+PrintGCDatestamps:输入 GC 的工夫戳(以日期的模式,如 2013-05-04T21: 53: 59.234 +0800)
  5. -XX:+PrintHeapAtGC:在进行 GC 的前后打印出堆的信息
  6. -Xloggc:…/logs/gc.log:日志文件的输入门路

接下来咱们应用示例代码进行演示一下参数看看

public class GCLogTest {public static void main( string[] args) {ArrayList<byte[]> list = new ArrayList<>();
        for (int i - o; i < 5oo; i++) {byte[] arr = new byte[1024 * 100];//100KB
            lilst.add(arr);
        }
    }
}

比如说咱们先应用:-XX:+PrintGCDetails参数看看打印出什么日志信息

此时咱们运行程序,看看具体是什么信息进去呢?

那么这些信息参数是什么意思呢?咱们先进行参数的解说看看具体有哪些?

咱们也能够再应用:XX:+PrintGC 参数看看打印出什么日志信息

此时咱们运行程序,看看具体是什么信息进去呢?

咱们也能够再应用:-XX:+PrintGCTimestamps:参数看看打印出什么日志信息

此时咱们运行程序,看看具体是什么信息进去呢?

咱们也能够再应用:-XX:+PrintGCDateStamps:参数看看打印出什么日志信息

此时咱们运行程序,看看具体是什么信息进去呢?

GC 日志补充阐明

================================

“[GC”和”[Full GC”阐明了这次垃圾收集的进展类型,如果有”Full”则阐明 GC 产生了”Stop The World”

应用 Serial 收集器在新生代的名字是 Default New Generation,因而显示的是”[DefNew”

应用 ParNew 收集器在新生代的名字会变成”[ParNew”,意思是”Parallel New Generation”

应用 Parallel scavenge 收集器在新生代的名字是”[PSYoungGen”

老年代的收集和新生代情理一样,名字也是收集器决定的

应用 G1 收集器的话,会显示为”garbage-first heap”

数据参数的示例阐明:

Allocation Failure 表明本次引起 GC 的起因是因为在年老代中没有足够的空间可能存储新的数据了。

[PSYoungGen: 5986K->696K(8704K) ] 5986K->704K (9216K)

  • 中括号内:GC 回收前年老代大小,回收后大小,(年老代总大小)
  • 括号外:GC 回收前年老代和老年代大小,回收后大小,(年老代和老年代总大小)

在应用-XX:+PrintGCTimestamps、-XX:+PrintGCDateStamps 参数打印信息能够这些信息

  • user 代表用户态回收耗时
  • sys 内核态回收耗时
  • real 理论耗时

因为多核线程切换的起因,工夫总和可能会超过 real 工夫

针对于 Young GC、Full GC 的阐明示意图

================================

接下来咱们应用示例代码来领会不同版本下的堆空间会产生什么?

public class GCLogTest1 {
    
    private static final int _1MB = 1024 * 1024;

    public static void testAllocation() {byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];
    }

    public static void main(String[] agrs) {testAllocation();
    }
}

接下来咱们进行参数的设置:-verbose:gc -Xms20M(堆空间初始大小)-Xmx20M(堆空间最大大小)-Xmn10M(新生代大小)-XX:+PrintGCDetails -XX:SurvivorRatio=8(新生代伊甸园区大小为 8MB,两个 Server 区各位 1MB)-XX:+UseSerialGC(应用 SerialGc)

接下来咱们演示一下 JDk 7 当中的状况是如何的呢?

此时咱们运行程序,看看具体是什么信息进去呢?

此时会有小伙伴问,咱们应用 SerialGc 时为什么是 total = 9216 呢?

而对于咱们的老年代总大小 10240k 与 6144k 又是什么状况呢?

此时咱们看看这参数的分配情况到底是什么意思呢?是个怎么回事呢?

接下来咱们演示一下 JDk 8 当中的状况是如何的呢?

此时咱们运行程序,看看具体是什么信息进去呢?

与 JDK7 不同的是,JDK8 间接断定 4M 的数组为大对象,间接怼到老年区去了

十、罕用日志剖析工具


咱们能够应用命令参数:-XLoggc:./logs/gc.log,寄存输入的地位(./ 示意当前目录),在 IDEA 中程序运行的当前目录是工程的根目录,而不是模块的根目录

咱们这里应用的 logs 文件,须要在我的项目创立目录文件

咱们应用下面示例代码进行应用看看

这时咱们将程序运行起来,就能够看见对应的 log 文件信息了

对于这些日志文件咱们举荐用一些工具去剖析这些 GC 日志,罕用的日志剖析工具有:

GCViewer、GCEasy、GCHisto、GCLogViewer、Hpjmeter、garbagecat 等

GCeasy(网址:gceasy.io)

GCViewer

参考资料


尚硅谷:JVM 虚拟机(宋红康老师)

正文完
 0