JVM层GC调优(上)

来源:Java填坑之路 2020年10月17日 12:53

JVM内存结构简介(jdk1.8)

JVM层的GC调优是出产环境上必不行少的一个环节,因为咱们需求断定这个进程能够占用多少内存,以及设定一些参数的阀值。以此来优化项意图功能和进步可用性,而且这也是在面试中经常会被问到的问题。

想要进行GC调优,咱们首要需求简略了解下JVM的内存结构,Java虚拟机的标准文档如下:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

在介绍JVM内存结构之前,咱们需求先知道运转时数据区这样的一个东西,它与JVM的内存结构有着必定的相关。不过它属所以一个标准,所以与JVM内存结构是有着物理上的差异的。运转时数据区如下:

1.程序计数器(Program Count Register,简称PC Register):JVM支撑多线程一起履行,每一个线程都有自己的PC Register。当每一个新线程被创立时,它都将得到它自己的PC Register。线程正在履行的办法叫做当时办法。假如履行的是Java办法,那么PC Register里寄存的就是当时正在履行的指令的地址,假如是native办法(C/C++编写的办法),则是为空。此内存区域是仅有一个在java虚拟机标准中没有规则任何OutOfMemoryError状况的区域。

2.虚拟机栈(JVM Stacks):

Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,它的生命周期与线程相同。虚拟机描绘的是Java办法履行的内存模型:每个办法在履行的一起都会创立一个栈帧,用于存储局部变量表、操作数栈、动态链接、办法出口等信息。每一个办法从调用直至履行完结的进程,就对应着一个栈帧在虚拟机栈中入栈到出栈的进程,实践上就是所谓的线程仓库。

局部变量表寄存了各种根本类型、方针引证和returnAddress类型(指向了一条字节码指令地址)。其间64位长度 long 和 double 占两个局部变量空间,其他只占一个。

该区域中规则的反常状况有两种:1.线程恳求的栈的深度大于虚拟机所答应的深度,将抛出StackOverflowError反常;2.假如虚拟机能够动态扩展,假如扩展时无法申请到满意的内存,就抛出OutOfMemoryError反常。

3.堆Heap:

Java堆(Java Heap)是Java虚拟机所办理的内存中最大的一块。堆是被一切线程同享的一块内存区域,在虚拟机发动时创立。此内存区域的仅有意图就是寄存方针实例,简直一切的方针实例都在这儿分配内存。

Java堆能够处于物理上不接连的内存空间中,只需逻辑上是接连的即可。堆中可细分为新生代和老时代,再细分可分为Eden空间、From Survivor空间、To Survivor空间。堆无法扩展时,会抛出OutOfMemoryError反常。

4.办法区(Method Area):

办法区与Java堆相同,是各个线程同享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。尽管Java虚拟机标准把办法区描绘为堆的一个逻辑部分,可是它却有一个别号叫做Non-Heap(非堆),意图是与Java堆差异开来。

当办法区无法满意内存分配需求时,抛出OutOfMemoryError

5.运转时常量池(Run-Time Constant Pool):

如上图所描绘的相同,它是办法区的一部分。Class文件中除了有类的版别、字段、办法、接口等描绘信息外,还有一项是常量池(Const Pool Table),用于寄存编译期生成的各种字面量和符号引证,这部分内容将在类加载后被放入办法区的运转时常量池中存储。并非预置入Class文件中常量池的内容才进入办法运转时常量池,运转期间也可能将新的常量放入池中,这种特性被开发人员运用得比较多的就是String类的intern()办法。

相同的,当办法区无法满意内存分配需求时,也会抛出OutOfMemoryError

6.本地办法栈(Native Method Stacks):本地办法栈与虚拟机栈所发挥的效果是十分相似的,它们之间的差异不过是虚拟机栈为虚拟机履行Java办法(字节码)效劳,而本地办法栈则为虚拟机运用到的Native办法效劳。

了解了运转时办法区标准后,咱们接下来看看JVM的内存结构图:

如上图,能够看到JVM内存被分为了两大区,非堆区用于存储方针以外的数据:

Metaspace:寄存Class、Package、Method、Field、字节码、常量池、符号引证等等

CCS:这个区域寄存32位指针的Class,也就是紧缩类空间,默许封闭,需求运用JVM参数敞开

CodeCache:寄存JIT编译后的本地代码以及JNI运用的C/C++代码

而堆区则用于存储方针相关数据:

Young:新生代,寄存新的或只经过几回Minor GC的方针

Eden:寄存最新创立的方针,一些较大的方针则会特别处理

S0/S1:当方针经过第一次Minor GC后,假如依然存活,就会寄存到这儿。需求留意的是,S0和S1区域在同一时刻上,只需其间一个是有数据的,而另一个则是空的。

Old:老时代,当S0或S1区域存满方针了,就会把这些方针寄存到这个old区域中

在图中也能够看到,堆区还被分为了年青代(young)和老时代(old)。那么为什么会有年青代:

咱们先来捋一捋,为什么需求把堆差异代?不分代不能完结它所做的作业么?其实不分代也完全能够,分代的仅有理由就是优化GC功能。你先想想,假如没有分代,那咱们一切的方针都会存在同一个空间里。当进行GC的时分,咱们就要找到哪些方针是没有用的,这样一来就需求对整个堆区进行扫描。而咱们的许多方针都是只存活一会儿的,所以GC就会比较频频,而每次GC都得扫描整个堆区,就会导致功能低下。不进行GC的话,又会导致内存空间很快被占满。

因为GC功能的原因,所以咱们才需求对堆区进行分代。假如进行分代的话,咱们就能够把新创立的方针专门寄存到一个独自的区域中,当进行GC的时分就优先把这块寄存“短寿”方针的区域进行收回,这样就会腾出很大的空间出来,而且因为不用去扫描整个堆区,也能极大进步GC的功能。

年青代中的GC:

从上图中也能够看到年青代被分为了三部分:1个Eden区和2个Survivor区,一般咱们都会简称为S0、S1(一起它们还分为from和to两种人物),默许份额为8:1。一般状况下,最新创立的方针都会被分配到Eden区(一些大方针会特别处理),这些方针经过第一次Minor GC后,假如依然存活,将会被移到Survivor区。方针在Survivor区中每熬过一次Minor GC,年纪就会添加1岁,当它的年纪添加到必定程度时,就会被移动到年迈代中。

因为年青代中的方针根本都是"短寿"的(80%以上),所以在年青代的废物收回算法运用的是仿制算法,仿制算法的根本思维就是将内存分为两块,每次只用其间一块,当这一块内存用完,就将还活着的方针仿制到别的一块上面。所以才会有S0和S1区,仿制算法的长处就是吞吐量高、可完成高速分配而且不会发作内存碎片,所以才适用于作为年青代的GC算法。

在GC开端的时分,方针只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中一切存活的方针都会被仿制到“To”,而在“From”区中,仍存活的方针会依据他们的年纪值来决议去向。年纪到达必定值(年纪阈值,能够经过-XX:MaxTenuringThreshold来设置)的方针会被移动到年迈代中,没有到达阈值的方针会被仿制到“To”区域。经过这次GC后,Eden区和From区现已被清空。这个时分,“From”和“To”会交流他们的人物,也就是新的“To”就是前次GC前的“From”,新的“From”就是前次GC前的“To”。不管怎样,都会确保名为To的Survivor区域是空的。Minor GC会一向重复这样的进程,直到“To”区被填满,“To”区被填满之后,会将一切方针移动到年迈代中。

JVM中的方针分配:

方针优先在Eden差异配

大方针则会直接进入老时代

咱们了解完JVM内存结构后,再来看看一些常用的JVM参数:

1.设置年青代的巨细,和年青代的最大值,详细的值需求依据实践事务场景进行判别。假如存在很多暂时方针就能够设置大一些,不然小一些,一般为整个堆巨细的1/3或许1/4。为了防止年青代的堆缩短,两个参数的值需设为相同大:

-XX:NewSize

-XX:MaxNewSize

2.设置Metaspace的巨细,和Metaspace的最大值,相同需设为相同大:

-XX:MetaspaceSize

-XX:MaxMetaspaceSize

3.设置Eden和其间一个Survivor的份额,这个值也比较重要:-XX:SurvivorRatio

4.设置young和old区的份额:-XX:NewRatio

5.这个参数用于显现每次Minor GC时Survivor区中各个年纪段的方针的巨细:-XX:+PrintTenuringDistribution

6.用于设置提升到老时代的方针年纪的最小值和最大值,每个方针在坚持过一次Minor GC之后,年纪就加1:

-XX:InitialTenuringThreshol

-XX:MaxTenuringThreshold

7.运用短直针,也就是启用紧缩类空间(CCS):-XX:+UseCompressedClassPointers

8.设置CCS空间的巨细,默许是一个G:-XX:CompressedClassSpaceSize

9.设置CodeCache的一个初始巨细:-XX:InitialCodeCacheSize

10.设置CodeCache的最大值:-XX:ReservedCodeCacheSize

11.设置多大的方针会被直接放进老时代:-XX:PretenureSizeThreshold

12.长时刻存活的方针会被放入Old区,运用以下参数设置就能够设置方针的最大存活年纪:-XX:MaxTenuringThreshold

注:假如设置为0的话,则年青代方针不经过Survivor区,直接进入年迈代。关于年迈代比较多的运用,能够进步功率。假如将此值设置为一个较大值,则年青代方针会在Survivor区进行屡次仿制,这样能够添加方针再年青代的存活时刻,添加在年青代即被收回的概论,linux64的java6默许值是15:

13.设置Young区每发作GC的时分,就打印有用的方针的岁数状况:-XX:+PrintTenuringDistribution

14.设置Survivor区发作GC后方针所存活的份额值:-XX:TargetSurvivorRatio

常见废物收回算法

本末节咱们来简略介绍一些常见的废物收回算法,众所周知Java差异与C/C++的一点就是,Java是能够主动进行废物收回的。所以在Java中的内存走漏概念和C/C++中的内存走漏概念不相同。在Java中,一个方针的指针一向被运用程序所持有得不到开释就属所以内存走漏。而C/C++则是把方针指针给弄丢了,该方针就永久无法得到开释,这就是C/C++里的内存走漏。

在进行废物收回的是时分,要怎么承认一个方针是否是废物呢?在很久以前有一种办法就是运用引证计数,当一个方针指针被其他方针所引证时就会进行一个计数。在进行废物收回时,只需这个计数存在,那么就会判别该方针就是存活的。而没有引证计数的方针,就会被判别为废物,能够进行收回。可是这种办法缺陷很明显,计数会占用资源不说,假如当一个A方针和一个B方针相互持有对方引证时,那么这两个方针的引证计数都不会为0,就永久不会被收回掉,这样就会导致内存走漏的问题。

在Java中,则是选用枚举根节点的办法:

思维:枚举根节点,做可达性剖析

根节点:能够是类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引证、本地办法栈的变量等等

如上图,JVM会从根节点开端遍历引证,只需顺着引证道路所遍历到的方针都会判别为存活方针,便是具有可达性的,这些方针就不会被收回。而没有被遍历到的方针,也就是图中的E和F方针,即使它们俩相互都还存在引证,也会被收回掉,因为它们不存在根节点的引证道路中,便是不具有可达性的。

已然了解了JVM怎么判别一个方针是否为废物后,咱们就能够来看看一些废物收回算法了:

1.符号-铲除:

算法:该算法分为“符号” 和 “铲除” 两个阶段:首要符号出一切需求收回的方针,在符号完结后一致进行收回

缺陷:功率不高,符号和铲除两个进程的功率都不高。简略发作内存碎片,碎片太多就会导致提早GC。

2.仿制算法:

算法:它将可用内存按容量区分为巨细持平的两个块,每次只运用其间的一块。当这一块内存用完了,就将还存活的方针仿制到另一个块上,然后再把已运用过的内存空间一次收拾掉。

优缺陷:完成简略,运转高效,吞吐量大,可是空间运用率低,一次只能运用50%

3.符号-收拾:

算法:符号进程依然与 “符号-铲除” 算法相同,当后续进程不是直接对可收回方针进行收拾,而是让一切存活的方针都向一端移动,然后直接收拾掉鸿沟以外的内存。

优缺陷:没有了内存碎片,可是收拾内存比较耗时

4.分代废物收回:算法:这就是现在JVM所运用的废物收回算法,能够看到以上所介绍到的算法都各自有优缺陷。而JVM就是把这些算法都整合了起来,在不同的区域运用不同的废物收回算法。Young区运用仿制算法,Old区则运用符号铲除或许符号收拾算法。

废物搜集器

在上一末节了解了一些常见的废物收回算法后,咱们再来看看JVM中常见的废物搜集器:

1.串行搜集器Serial:Serial、Serial Old

2.并行搜集器Parallel:Parallel Scavenge、Parallel Old,吞吐量优先,是Server形式下的默许搜集器。默许在内存大于2G,CPU核心数大于2核的环境下为Server形式

3.并发搜集器Concurrent:CMS、G1,中止时刻优先

注:串行搜集器简直不会在web运用中运用,所以首要介绍并行和并发搜集器

串行 VS 并行 VS 并发:

串行(Serial):指只需单个废物搜集线程进行作业,也就是单线程的,当废物搜集线程发动的时分,用户线程会处于一个等候状况。适宜内存较小的嵌入式开发中

并行(Parallel):指多条废物搜集线程并行作业,但此刻用户线程依然处于等候状况。适宜科学核算、后台处理等弱交互场景

并发(Concurrent):指用户线程与废物搜集线程一起履行(但不必定是并行的,可能会替换履行),废物搜集线程在履行的时分不会中止用户程序的运转。适宜对呼应时刻有要求、交互性强的场景,比方Web开发

中止时刻 VS 吞吐量:

中止时刻:指废物搜集器在进行废物收回时所中止运用履行的时刻。能够运用以下参数进行设置:

-XX:MaxGCPauseMillis

吞吐量:指花在废物搜集的时刻和花在运用时刻的占比。能够运用以下参数进行设置:

-XX:GCTimeRatio=

废物搜集时刻占:1/1+n

敞开串行搜集器:

-XX:+UseSerialGC(Young区)

-XX:+UseSerialOldGC(Old区)

敞开并行搜集器:

-XX:+UseParallelGC(Young区)

-XX:+UseParallelOldGC(Old区)

-XX:ParallelGCThread=

设置N个GC线程,N取决于CPU核心数

并发搜集器在JDK1.8里有两个,一个是CMS,CMS因为具有呼应时刻优先的特色,所所以低推迟、低中止的,CMS是老时代搜集器。敞开该搜集器的参数如下:

-XX:+UseParNewGC(Young区)

-XX:+UseConcMarkSweepGC(Old区)

另一个是G1,敞开该搜集器的参数如下:-XX:+UseG1GC(Young区、Old区)

废物搜集器调配图:

注:实线代表可调配运用的,虚线表明当内存分配失利的时分CMS会退化成SerialOld。JDK1.8中主张运用的是G1搜集器

有这么多的废物搜集器,那么咱们要怎么去挑选适宜的废物搜集器呢?这个是没有详细答案的,都得依照实践的场景进行挑选,但一般都会依照以下准则来进行挑选:

优先调整堆的巨细让效劳器自己来挑选

假如内存小于100M,运用串行搜集器

怎么是单核,而且没有中止时刻的要求,就能够运用串行或由JVM自己挑选

假如答应中止时刻超越1秒,挑选并行或许JVM自己挑选

假如呼应时刻最重要,而且不能超越1秒,则运用并发搜集器

其间并行搜集器是支撑自适应的,经过设置以下几个参数,并行搜集器会以中止时刻优先去动态调整参数:

-XX:MaxGCPauseMillis=

-XX:GCTimeRatio=

-Xmx

当内存不行的时分并行搜集器能够动态调整内存,尽管实践出产环境顶用的比较少,至于每次动态调整多少内存,则运用以下参数进行设置:

-XX:YoungGenerationSizeIncrement=

(添加,Young区,默许20%)

-XX:TenuredGenerationSizeIncrement=

(添加,Old区,默许20%)

-XX:AdaptiveSizeDecrementScaleFactor=

(削减,默许4%)

了解了并行搜集器后,咱们来简略看看CMS搜集器其他的一些特性以及相关调优参数。

CMS废物搜集进程:

1.CMS initial mark:初识符号Root,STW

2.CMS concurrent mark:并发符号

3.CMS-concurrent-preclean:并发预收拾

4.CMS remark:从头符号,STW

5.CMS concurrent sweep:并发铲除

6.CMS concurrent-reset:并发重置

CMS的缺陷:

CPU灵敏

会发作起浮废物

会发作空间碎片

CMS的相关调优参数:

设置并发的GC线程数:-XX:ConcGCThreads

敞开以下参数能够在Full GC之后对内存进行一个紧缩,以此削减空间碎片:-XX:+UseCMSCompactAtFullCollection

这个参数则是设置多少次Full GC之后才进行紧缩:-XX:CMSFullGCsBeforeCompaction

设置Old区存满多少方针的时分触发Full GC,默许值为92%:-XX:CMSInitiatingOccupancyFraction

启用该参数表明不行动态调整以上参数的值:-XX:+UseCMSInitiatingOccupancyOnly

启用该参数表明在Full GC之前先做Young GC:-XX:+CMSScavengeBeforeRemark

在jdk1.7之前能够运用以下参数,启用收回Perm区:-XX:+CMSClassUnloadingEnable

在jdk1.8后,引荐运用的废物搜集器是G1。G1搜集器在jdk1.7中第一次呈现,所以到了jdk1.8里就十分成熟了。

G1搜集器官网介绍如下:

The Garbage-First (G1) garbage collector is fully supported in Oracle JDK 7 update 4 and later releases. The G1 collector is a server-style garbage collector, targeted for multi-processor machines with large memories. It meets garbage collection (GC) pause time goals with high probability, while achieving high throughput. Whole-heap operations, such as global marking, are performed concurrently with the application threads. This prevents interruptions proportional to heap or live-data size.

The first focus of G1 is to provide a solution for users running applications that require large heaps with limited GC latency. This means heap sizes of around 6GB or larger, and stable and predictable pause time below 0.5 seconds.

官方文档地址:http://www.oracle.com/technetwork/java/javase/tech/g1-intro-jsp-135488.html

原理概述:

G1 也是归于分代搜集器的,可是G1的分代是逻辑上的,而不是物理上的

G1 将整个对区域区分为若干个Region,每个Region的巨细是2的倍数(1M,2M,4M,8M,16M,32M,经过设置堆的巨细和Region数量核算得出。

Region区域区分与其他搜集相似,不同的是独自将大方针分配到了独自的region中,会分配一组接连的Region区域(Humongous start 和 humonous Contoinue 组成),所以一共有四类Region(Eden,Survior,Humongous和Old),G1 效果于整个堆内存区域,规划的意图就是削减Full GC的发作。在Full GC进程中因为G1 是单线程进行,会发作较长时刻的中止。

G1的OldGc符号进程能够和yongGc并行履行,可是OldGc必定在YongGc之后履行,即MixedGc在yongGC之后履行。

结构图:

G1废物搜集算法首要运用在多CPU大内存的效劳中,在满意高吞吐量的一起,尽可能的满意废物收回时的暂停时刻,该规划首要针对如下运用场景:

废物搜集线程和运用线程并发履行,和CMS相同

闲暇内存紧缩时防止冗长的暂停时刻

运用需求更多可猜测的GC暂停时刻

不期望献身太多的吞吐功能

G1的几个概念:

Region:G1搜集器所区分的内存区域

SATB:Snapshot-At-TheBeginning,它是经过Root Tracing得到的,GC开端时分存活方针的快照

RSet:记录了其他Region中的方针,引证本Region中方针的联系,归于points-into结构(谁引证了我的方针)

G1中的Young GC进程,和以往的是相同的:

新方针进入Eden区

存活方针拷贝到Survivor区

存活时刻到达年纪阈值时,方针提升到Old区

可是G1中没有Full GC,取而代之的是Mixed GC:它不是Full GC,所以触发Mixed GC时收回的是一切的Young区和部分Old区的废物

G1里还有一个概念叫大局并发符号(global concurrent marking),和CMS的并发符号是相似的:

1.Initial marking phase:符号GC Root,STW

2.Root region scanning phase:根区扫描

3.Concurrent marking phase:并发符号存活方针

4.Remark phase:从头符号,STW

Cleanup phase:部分STW

G1相关调优参数:

设置堆占有率到达这个参数值则触发global concurrent marking,默许值为45%:-XX:InitiatingHeapOccupancyPercent

设置在global concurrent marking完毕之后,能够知道Region里有多少空间要被收回,在每次YGC之后和再次发作Mixed GC之前,会查看废物占比是否到达此参数的值,只需到达了,下次才会发作Mixed GC:-XX:G1HeapWastePercent

设置Old区的Region被收回时的存活方针占比:-XX:G1MixedGCLiveThresholdPercent

设置一次global concurrent marking之后,最多履行Mixed GC的次数:-XX:G1MixedGCCountTarget

设置一次Mixed GC中能被选入CSet的最多Old区的Region数量:-XX:G1OldCSetRegionThresholdPercent

其他参数:

-XX:+UseG1GC //敞开G1搜集器

-XX:G1HeapRegionSize=n //设置Region的巨细,巨细规模:1-32M,数量上限:2048个

-XX:MaxGCPauseMillis=200 //设置最大中止时刻

-XX:G1NewSizePercent //设置Young区巨细

-XX:G1MaxNewSizePercent //设置Young区最大占整个Java Heap的巨细,默许值为60%

-XX:G1ReservePercent=10 //保存防止to space溢出

-XX:ParallelGCThreads=n //设置SWT线程数

-XX:ConcGCThreads=n //并发线程数=1/4*并行

留意事项:

年青代巨细:防止运用-Xmn、-XX:NewRatio等显式设置Young区巨细,会掩盖暂停时刻方针

暂停时刻方针:暂停时刻不要太苛刻,其吞吐量方针是90%的运用程序时刻和10%的废物收回时刻,太苛刻会直接影响到吞吐量

至所以否需求切换到G1搜集器,能够依据以下准则进行挑选:

50%以上的堆被存活方针占用

方针分配和提升的速度改变十分大

废物收回时刻特别长,超越了1秒

关于在Web运用中,怎么判别一个废物搜集器的好坏,首要是看以下两点,以下两点都需为优才是好的废物搜集器:

1.呼应时刻

2.吞吐量

相关推荐
最新文章