JVM设置(调优)

  • Post author:
  • Post category:其他



目录


基本命令


收集器


Serial收集器


ParNew收集器


Parallel Scavenge收集器


Serial Old


Parallel Old


CMS收集器


G1收集器


ZGC收集器


排查案例


案例1


案例2


案例3


基本命令

1). 参数查询



java



-server  选择 “server” VM  默认 VM 是 server.

-cp  <目录和 zip/jar 文件的类搜索路径>

-classpath <目录和 zip/jar 文件的类搜索路径>   用 ; 分隔的目录,

JAR 档案 和 ZIP 档案列表, 用于搜索类文件。



-D<名称>=<值>


设置系统属性 ,(有时候 可以用 这个选项 给 jvm设置 系统属性,用于逻辑判断)




java -X



-Xms<size>        设置初始 Java 堆大小

-Xmx<size>        设置最大 Java 堆大小

-Xss<size>        设置 Java 线程堆栈大小

-Xloggc:<file>    将 GC 状态记录在文件中 (带时间戳)

java -XX:+PrintFlagsFinal  查看所有的 jvm 设置项


2). 堆设置

首先设置堆初始大小 最大大小

-Xms2g

-Xmx2g


2个设置年轻代的参数(老年代=总大小-年轻代大小):

最小空间: -XX:newSize=1g

最大空间: -XX:MaxnewSize=1g

同时设置 最小空间和最大空间: -Xmn1g

年轻代与老年代的比例:

-XX:NewRatio=2 (默认值),表示年轻代:老年代 = 1:2

年轻代的 Eden、Survivor 比例设置 参数:

-XX:SurvivorRatio=8  (默认值), from:to:eden = 1:1:8

年轻代设置的大,可以减少 gc 次数。如果年轻代太小,可能会频繁gc,导致 STW 很长

如何计算新生代老年代到底分配了多少空间?

一般来说,通过 堆最小空间/3 得到的是 年轻带最小空间,堆最大空间/3 得到的是 年轻代最大空间,因为 年轻代:老年代 = 1:2 ,年轻代是占了 1/3的空间。 得到了年轻代的空间,剩下的就是老年代的空间。

3). 进入老年代 年龄设置

-XX:MaxTenuringThreshold=15 (默认值) 表示 一个对象 经历了 15次 gc 依然存活,则进入老年代

4). 打印gc信息

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCDateStamps  gc发生的时间信息

-XX:+HeapDumpOnOutOfMemoryError  oom时 dump

-XX:HeapDumpPath=e:\d.txt 可以指定dump 文件

5). jdk 自带工具

jps 可以 进程号

jinfo 查看及设置 进程 配置信息

jstack 查询 某进程  所有的 线程栈 信息


jstat jdk自带的分析工具

<option>包括:

-class

-compiler

-gc

-gccapacity

-gccause

-gcmetacapacity

-gcnew

-gcnewcapacity

-gcold

-gcoldcapacity

-gcutil

-printcompilation

查看gc情况:

jstat -gc 19896(进程号) 1000(打印间隔时间)

查看老年代、新生代空间分配、使用情况:

jstat -gcoldcapacity pid

jstat -gcnewcapacity pid

jmap

jmap -histo pid

输出所有对象,包含 有效和无效对象(没有引用的对象 也被输出)

不触发GC

jmap -histo:live

输出 存活的对象。

会触发一次GC,然后把剩下存活的对象输出

jmap -dump:format=b,file=e:/a.bin  pid

dump堆对象,只包含存活的对象

不触发GC

jmap -dump:

live,

format=b,file=e:/a.bin  pid

dump堆对象,只包含存活的对象

触发GC

jmap -heap 19896  to print java heap summary

#jvm 参数:  -Xms2g -Xmx6g  -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m 
jmap -heap 18680
Attaching to process ID 18680, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11

using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6442450944 (6144.0MB) #堆最大 
   NewSize                  = 715653120 (682.5MB)  #新生代最小值: 1/3 * (堆初始空间)
   MaxNewSize               = 2147483648 (2048.0MB)#新生代最大值: 1/3 * (堆最大空间)
   OldSize                  = 1431830528 (1365.5MB)#老年代最小值:(堆初始空间 - NewSize)
   NewRatio                 = 2 #新生代 老年代默认占比: 1(新生代):2(老年代)
   SurvivorRatio            = 8 #新生代中 eden:from:to 8:1:1
   MetaspaceSize            = 134217728 (128.0MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 335544320 (320.0MB)
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 710410240 (677.5MB)
   used     = 581433808 (554.4984893798828MB)
   free     = 128976432 (123.00151062011719MB)
   81.8447954804255% used
From Space:
   capacity = 2621440 (2.5MB)
   used     = 1277984 (1.218780517578125MB)
   free     = 1343456 (1.281219482421875MB)
   48.751220703125% used
To Space:
   capacity = 2621440 (2.5MB)
   used     = 0 (0.0MB)
   free     = 2621440 (2.5MB)
   0.0% used
PS Old Generation
   capacity = 1431830528 (1365.5MB)
   used     = 1018873976 (971.673942565918MB)
   free     = 412956552 (393.82605743408203MB)
   71.15883870859889% used



#JVM 参数 -Xms2g -Xmx6g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m
Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 6442450944 (6144.0MB)
   NewSize                  = 1073741824 (1024.0MB)
   MaxNewSize               = 1073741824 (1024.0MB)
   OldSize                  = 1073741824 (1024.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 134217728 (128.0MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 335544320 (320.0MB)
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 1035993088 (988.0MB)
   used     = 143000784 (136.3761749267578MB)
   free     = 892992304 (851.6238250732422MB)
   13.803256571534192% used
From Space:
   capacity = 9437184 (9.0MB)
   used     = 9404480 (8.96881103515625MB)
   free     = 32704 (0.03118896484375MB)
   99.65345594618056% used
To Space:
   capacity = 18874368 (18.0MB)
   used     = 0 (0.0MB)
   free     = 18874368 (18.0MB)
   0.0% used
PS Old Generation
   capacity = 1451753472 (1384.5MB)
   used     = 1184475088 (1129.6034698486328MB)
   free     = 267278384 (254.8965301513672MB)
   81.58927192839529% used

6) 其他


Parallel Scavenge 收集器

查看是否开启  自适应大小功能

jinfo -flag UseAdaptiveSizePolicy  pid

-XX:+UseAdaptiveSizePolicy  // 表示开启

由于AdaptiveSizePolicy会动态调整 Eden、Survivor 的大小。

有些情况存在Survivor 被自动调为很小,这个时候YGC回收掉 Eden区后,

还存活的对象进入Survivor 装不下,就会直接晋升到老年代,

导致老年代占用空间逐渐增加,从而触发FULL GC,

可能导致 FULL GC的耗时很长(比如到达几百毫秒)。

可以关闭这个功能:

-XX:-UseAdaptiveSizePolicy


查看使用的是哪一种垃圾收集器:

查看默认垃圾收集器:

java -XX:+PrintCommandLineFlags -version

查看当前JVM程序使用的垃圾收集器:

可以使用JDK自带的

jconsole.exe

来查看 使用的是哪种垃圾收集器

也可以使用

jinfo pid

查看进程信息,其中会有 垃圾收集器的参数配置

各种垃圾收集器的参数名,可以通过打印所有的虚拟机参数查看,

如果需要修改垃圾收集器,则通过 -XX:+垃圾收集器名字

UseSerialGC

UseParNewGC

UseParallelGC

UseParallelOldGC

UseConcMarkSweepGC

UseG1GC

内存管理

以下内容由个人 总结,并记录自己的理解

JVM 启动后,会获取到操作系统为其分配的内存。虚拟机管理这些内存,是通过 将内存划分出不同的内存块,每一块内存 用来存储 不同的数据。

因此,需要明确的一点规则是: 虚拟机管理内存 是 先对内存 进行划分后,再进行管理。

内存划分的区域包括:

程序计数器(pc)、栈(stack)、方法区(method area)、堆(heap area)




分为:虚拟机栈 和 本地方法栈

栈,是属于线程私有的,每个线程都会创建一个独立的栈空间,归当前线程私有使用。栈中存放的是 栈帧,栈帧是每个方法执行时 需要的存储空间。

比如 写一个方法时,会有方法入参、返回值、局部变量(基本类型的变量和引用类型的变量)、也会调用另一个方法,这时需要存储 方法返回的 指令地址。 方法中需要使用到的 数据 都是存储在栈帧中的空间内。StackOverflowError  OutOfMemoryError



heap 主要用来存储 对象实例。为了重复利用堆空间,对那些 不再使用的对象 所占用的 内存 需要 及时回收。因此,堆空间 也是 GC 收集器主要管理的内存空间。


堆内存回收,需要考虑

三个W,Who、When、How

1. 哪些对象需要被回收,也就是 需要 标识出 需要被回收的对象

2. 什么时候回收,也就是 触发GC线程执行的时机

3. 如何回收,也就是需要确定 回收算法


Who

第一步

标识

,是比较难的。

现在都是使用

引用可达性

算法来确定 对象存活。但是 在程序运行过程中,总是会有新的 die 对象的产生,也有一些可能die的对象,最后又活了过来。因此,STW  Stop The World,此时会暂停所有的用户线程,造成了 整个JVM被按了暂定键,这时 服务无法对 外提供正常的响应,对于很多 业务来说,很影响用户的使用体验。而 尽量缩短 STW的时间长度,也是GC的一个目标。

知识点1:算法

可达性 分析 Reachability Analysis 的一些词汇:

GC Roots  根对象

引用链 Reference Chain

GC Roots的对象主要包括:

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

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

在方法区中常量引用的对象

知识点2:引用类型

引用可达性算法 来 标识  哪些 对象 需要被回收。重要的一个判断依据就是 GC Root 对象 引用的。不同的引用类型,GC的操作也是不同的。需要分析一下 引用。

Java中提供了 4种引用类型:


强引用

Strongly Reference

强引用,是存在的最多的一种引用。Object o = new Object(); 通过这种 = 赋值的引用,都是强引用。无论任何情况下,只要

强引用关系还存在

,垃圾收集器就

永远不会回收

掉被引用的对象


软引用

Soft Reference

软引用,表示 还有用,但非必须的对象。比如:缓存的数据。如果

内存快不足时

,就会考虑

回收软引用 引用的对象

。即使 软引用关系还存在。


弱引用

Weak Reference

弱印象,描述那些非必须对象,但是 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,

都会回收

掉只被弱引用关联的对象。(实际测试发现,并不是这样,发生GC 也会存在不回收的情况,因为内存足够大。)


虚引用

Phantom Reference

知识点3:软 弱 对比

软引用和弱引用 分别在什么场合下使用呢?

软引用主要是 内存不够时 会回收,弱引用是 GC发生 就回收。对于软引用的作用,还好理解,而为什么要定义 一个 GC时就会回收的引用呢?

一个对象,可能会被多个 引用变量引用着。 所以需要 有这种 引用 的 区分。一个引用 是主要的引用,另一个引用是次要的引用。当主要的引用 不存在时,那么 次要的引用 也不需要存在了。这时,就可以用强引用和弱引用同时 引用这个对象。只要强引用不存在了,那么弱引用就不能存在,即 虽然存在弱引用关系,也需要回收这个对象。所以,弱引用的目的就在这。同理,对于软引用,也可以 表达出 不同强度的引用关系,能表达出主次关系来,当主要的引用关系不存在时,如何处理被次要引用 引用着的对象。

知识点4:最后的救赎

protected void finalize() throws Throwable { }

这个方法是Object 类提供的,虚拟机对那些重写了 finalize方法的 没有 GC Root 引用的对象 处理规则是:

1.   将这些对象放入 F-Queue,虚拟机会 自动创建一个 低优先级的 Finalizer 线程 来执行 finalize方法。这里会存在一个问题,如果一个对象的finalizer执行了很长时间,那么其他对象也得等,这就造成了 对象得不到及时回收的问题。

2. F-Queue队列的对象 都执行了 finalizer方法后,再对 队列中的对象 进行 一次标记。如果 标记时发现 存在GC Root 引用,那么不会进行回收;对于仍然没有GC Root引用的对象,则进行 回收。

3. finalizer 方法 只能被执行一次,如果执行后,下一次 就会直接回收。

此图是 Finalizer线程正在执行 finalizer方法:

收集器

Serial收集器



新生代





收集器


标记







复制



算法


单线程


收集器


进行垃圾收集时,必须



暂停


其他所有


工作线程



,直到它收集结束 STW

由虚拟机在后台


自动发起





自动完成


的,在用户


不可知、不可控


的情况

下把




用户




的正常工作的




线程




全部




暂停


简单理解:

发生垃圾收集时,JVM内部 所有的用户线程 全部暂停,似乎整个世界被冻结住了,或者想象成 JVM世界 突然消失了。 而 JVM内部 整个世界 只有 一个 垃圾收集线程 在执行着,直到  这仅有的一个垃圾收集线程 工作结束(把所有的垃圾 标记完,复制完),用户线程才恢复。


ParNew收集器



新生代





收集器


标记







复制



算法


多线程并行


收集器


实质上是


Serial


收集器的多线程




并行




版本

使用多条线程进行垃圾收集

简单理解:


同Serial,只是 JVM世界被冻结后, 有 多条 垃圾收集线程 在工作。



·

并行



Parallel

):并行描述的是

多条垃圾收集器线程之间的关系

,说明同一时间有多条这样的线



程在协同工作,通常默认此时

用户线程是处于等待状态





·

并发



Concurrent

):并发描述的是

垃圾收集器线程与用户线程之间的关系

,说明

同一时间垃圾





收集器线程与用户线程都在运行

。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于 垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

Parallel Scavenge收集器



新生代





收集器



标记







复制



算法



多线程



并行


收集



吞吐量优先


目标是达到一个



可控制的 吞吐量






Throughput


)。

所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值


吞吐量

= 用户线程运行

时间

/ (用户线程运行

时间

+ 垃圾收集线程运行

时间

)

对于保证吞吐量可以得到什么结果?



A/(A+B) 越高 =》 (A+B)/A 越低 =》 1 + B/A 越低 => B/A 越低 => 垃圾收集运行时间越低 => 垃圾收集越快=》需要回收的空间越小=》自动把 新生代空间 压缩到很小=》频繁GC或提前进入老年代



停顿时间越短

(



适合多交互、高响应

) 就越适合需要与用户交互或需要保证服务响应质量的程序,良 好的响应速度能提升用户体验;


高吞吐量



(

适合少交互

) 则可以最高效率地利用处理器资源,尽快完成程序的运算 任务,主要适合在后台运算而不需要太多交互的分析任务


-XX





MaxGCPauseMillis

值是一个大于


0的毫秒数;

收集器将尽力保证

内存回收花费的时间

不超过用户设定值


-XX





GCTimeRatio

值则应当是一个大于


0


小于


100的整数;


垃圾收集时间占总时间的 比率,相当于吞吐量的倒数。譬如把此参数设置为


19


,那允许的最大垃圾收集时间就占总时间的5% (即


1/(1+19)


),默认值为


99


,即允许

最大




1%



(即


1/(1+99)


)的垃圾收集时间。


-XX:+UseAdaptiveSizePolicy    自适应的调节策略


开关参数,当这个参数被激活之后,就不需要人工指定新生代的大小(-Xmn


)、


Eden


与Survivor区 的


比例


(-XX





SurvivorRatio)、


晋升


老年代对象大小(-XX


:PretenureSizeThreshold)等细节参数 了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时 间或者最大的吞吐量

Serial Old



老年代

收集器



标记-整理



算法


单线程



收集器



对应 Serial



JDK 5


以及之前的版本中与


Parallel Scavenge


收集器搭配使用

作为CMS 收集器发生失败时的后备预案,在并发收集发生


Concurrent Mode Failure


时使用

Parallel Old



老年代

收集器



标记-整理



算法


多线程 并发

收集器

对应

Parallel Scavenge

CMS收集器


Concurrent Mark Sweep

以获取



最短



回收



停顿时间



为目标

B/S系统   希望系统停顿时间尽可能短,以给用户带来良好的交互体验



标记







清除



算法

1


)初始标记(


CMS initial mark




仅仅只是标记一下GC Roots能

直接关联到的对象



速度很快





STW


2


)并发标记(


CMS concurrent mark




从GC Roots的直接关联对象开始

遍历整个对象

图的过程,这个过程

耗时较长



不用STW


3


)重新标记(


CMS remark





修正

并发标记期间,因用户程序继续运作而导致

标记

产生

变动

的那一部分对象的

标记记录


比初始标记稍长,但远比并发标记短



STW



4


)并发清除(


CMS concurrent sweep




清理删除掉标记阶段判断的已经死亡的 对象,由于不需要移动存活对象


不用STW


由于在整个过程中

耗时最长

的并发标记和并发清除阶段中,垃圾收集器线程都可以与用户线程

一 起工作

,所以从总体上来说,


CMS


收集器的内存回收过程是与用户线程一起

并发执行




优点



并发收集、低停顿


“并发低停顿收集器” Concurrent Low Pause Collector


缺点



1.

对CPU资源非常敏感,占用CPU

计算能力

而导致应用程序变慢,降低总吞吐量

CMS默认启动的回收线程数是(处理器核心数量 +3





/4


如果处理器核心数在四个或以上:


并发回收时垃圾收集线程只占用不超过25%的 处理器运算资源,并且会随着处理器核心
数量的增而下降
如果

处理器核心数量不足四个:

CMS对用户程序的影响就可能变得很大

如果应用本来的处理器负载就很高,还要分出一半的运算能 力去执行收集器线程,就可能导致用户程序的执行速度忽然大幅降低

2.

无法处理“浮动垃圾”


(Floating Garbage




有可能出现“Con-current Mode Failure”


失败进而导致另一次完全


“Stop The World”





Full GC


的产生


浮动垃圾:

在CMS的并发标记和并发清理阶 段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分 垃圾对象是出现在标记过程结束以后,


CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集 时再清理掉。


预留空间


同样也是由于在垃圾收集阶段用户线程还需要持续运行,那就还需要预留足够内存空间提供给用户线程使用,因此


CMS收集器不能像其他收集器那样等待 到老年代几乎完全被填满了再进行收集,必须预留一部分空间供并发收集时的程序运作使用。


在JDK 5


CMS


收集器当老年代使用了


68%的空间后就会被激活

-XX





CMSInitiatingOccu-pancyFraction

在JDK 6

CMS收集器的启动阈值就已经默认提升至92%


更容易面临另一种风险:要是


CMS运行期间

预留的内存无法满足程序分配新对象的需要

,就会出现一次






并发失败









Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时

启用




Serial Old收集器来重新进行老年代的垃圾收集

, 但这样停顿时间就很长了


3.

CMS


是一款基于






标记-清除



”算法实现的收集器,会有大量空间



碎片



产生,

无法找 到足够大的连续空间来分配当前对象,而不得不提前触发一次Full GC的情况,

-XX





+UseCMS-CompactAtFullCollection


开关参数

(默认是开启的,此参数从


JDK 9开始废弃




用于在


CMS


收集器不得不进行



Full GC时

开启内存

碎片



合并整理

过程,由于这个 内存整理必须移

存活对象,是



无法并发



的。

-XX


:CMSFullGCsBefore- Compaction


(此参数从


JDK 9开始废弃




要求


CMS收集器在执行过

若干次

(数量 由参数值决定)不整理空间的


Full GC


之后,下一次进入



Full GC



前会先进行碎片整理(默认值为


0,表 示每次进入


Full GC


时都进行碎片整理)。



主要是标记-清楚,到时空间碎片, 控制一下 是 每次full gc 时 都进行 碎片整理 还是 执行 若干次 full gc 时 再进行碎片整理。

G1收集器


Garbage First


全功能的垃圾收集器 Fully-Featured Garbage Collector


面向服务端


JDK 9  服务端模式下的默认垃圾收集器



延迟可控

的情况下获得

尽可能高的吞吐 量


停顿时间模型:
能够支持指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过N毫秒这样的目标
Mixed GC模式:

面向堆内存任何部分来组成回收集(


Collection Set


,一般简称


CSet)进行回收

,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。

基于



Region



的堆内存布局:

G1不再坚持固定大小以及固定数量的 分代区域划分,而是把连续的


Java


堆划分为



多个大小相等的独立区域(Region)



,每一个


Region


都可以


根据需要,扮演

新生代




Eden


空间、


Survivor空间,或者

老年代

空间。收集器能够对扮演

不同角色的 Region采用不同的策略去处理

,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果



Humongous



区域:

专门用来

存储大对象


只要大小

超过

了一个 Region


容量

一半

的对象即可判定为大对象


超级大对象

:将会被存放在



N


个连续的


Humongous Region



之中

G1


的大多数行为都把


Humongous Region作为老年代 的一部分来进行看待


每个


Region


的大小可以通过参数


-XX





G1HeapRegionSize设 定,取值范围为



1MB







32MB



,且应为



2





N


次幂


Region作为

单次回收的

最小单元



每次收集到的内存空间都是


Region


大小的整数倍


价值





即回收所获得的空间大小以及回收所需时间的经验值



空间 和 时间


优先级列表





每次根据用户设定允许的收集停顿时间(使用参数-XX





MaxGCPauseMillis


指定,默

认值是


200


毫秒),优先处理回收价值收益最大的那些


Region



时间一定下:



价值最大化


在有限的时间内获 取尽可能高的

收集效率



四个步骤




初始标记



短暂STW


是标记一下GC Roots


能直接关联到的对象,并且修改TAMS 指针的值



并发标记



不STW


对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象

重新处理SATB


记录下的在并发时有引用变动的对象



最终标记



短暂STW


处理并发阶段结束后仍遗留下来的最后那少量的SATB


记录



筛选回收



短暂STW


收价值和成本进行排序

把决定回收的那一部分Region


的存活对象复制到空的


Region中,再清理掉整个旧Region


的全部空间



为了保证吞吐量所以才选择了完全暂停用户线程的实现方案


默认的停顿目标为两百毫秒


一般来说,回收阶段占到几十到一百甚至 接近两百毫秒都很正常

如果由于停顿目标时间太短,导致每次选出来的回收集只占堆内存很小的一部分,收集器收集的速

度逐渐跟不上分配器分配的速度,导致垃圾慢慢堆积。很可能一开始收集器还能从空闲的堆内存中获 得一些喘息的时间,但应用运行时间一长就不行了,

最终占满堆

引发


Full GC反而降低性能,所以通常 把期望停顿时间设置为一两百毫秒或者两三百毫秒会是比较合理的




G1


开始,最先进的垃圾收集器的设计导向都不约而同地变为追求能够应付应用的内存分配速率




Allocation Rate


),而不追求一次把整个


Java堆全部清理干净。这样,应用在分配,同时收集器在收 集,只要



收集的速度能跟得上对象分配的速度




那一切就能运作得很完美。


G1


从整体来看是基于






标记





整理






算法实现的收集器,但从局部(两个Region 之间)上看又是基于






标记





复制






算法实现



G1运作期间不会产生内存 空间碎片



,垃圾收集完成之后能提供规整的可用内存

CMS VS G1

内存占用:

G1


的卡表占内存多


CMS


的卡表占内存少

执行负载:


ZGC收集器


Z Garbage Collector

JDK 11

希望在尽可能对吞吐量影响不太大的前提下


,实现 在任意堆内存大小下都可以把垃圾收集的停顿时间限制在

十毫秒以内

的低延迟

ZGC


收集器是一款

基于




Region内存布局

的,(暂时) 不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现

可并发的标记




-整理算法

的,

以低 延迟为首要目标

的一款垃圾收集器

内存布局:

基于


Region


的堆内存布局





具有动态性



——动态创建和销毁,以及动态的区域容量大小,必须为


2MB


的整数倍


并发整理算法:




染色指针技术





直接把标记信息记在引用对象的指针上





64位系统



中,理论可以访问的内存高达


16EB(



2的64次幂



)字节,在


AMD64


架构


中只支持到



52



位。

尽管Linux下64位指针的高


18位


不能用来寻址,但


剩余的46位


指针所能支持的64TB内存在今天仍然能够充分满足大型服务器的需要。鉴于此,ZGC的染色指针技术继续盯上了这剩下的46位指针宽度,将其


高4位


提取出来存储四个标志信息


ZGC能够管理的内存不可以超过


4TB(2





42


次幂)



染色指针的三大优势





四个阶段:

并发标记





Concurrent Mark




短暂STW


并发预备重分配





Concurrent Prepare for Relocate





并发重分配





Concurrent Relocate




并发重映射





Concurrent Remap




排查案例

案例1

OOM:java.lang.OutOfMemoryError: GC overhead limit exceeded

这是一种 内存不够的 错误类型。 意思 每次 gc 回收的内存 非常少。

出现这种异常,先直接 调大 JVM 的内存分配

案例2

应用服务卡死,无响应,OOM 一般排查思路:

1. 查看 服务资源占用情况:


top

命令

2. 查看 是否出现 内存不足导致 进程被杀掉的情况 :

使用Linux 自带命令

dmesg

| grep java ,或者 dmesg | grep ‘kill|oom|out of memory’

是否有这种: Out of memory: Kill process 28754 (java) score 210 or sacrifice child

3. 查看服务pid

ps -aux | grep java

4. 查看 gc 情况

jstat -gcutil pid 1000

5. 查看 存活对象分布情况

jmap -histo:live pid

6.

导出dump 文件

,一般 分析内存OOM 都需要 导出dump文件,这步是必须的。

jmap -dump:format=b,file=/pid.bin

7. 分析dump文件

网上查看的一般是MAT

案例3

现象:top命令 发现 cpu 占用极高,jstat 频繁gc

怀疑:可能是出现了死循环

排查:

1. 查看 堆内的对象情况

jmap -histo 46800 和 jmap -histo:live 46800 对比后,发现

   1:         51031        4212360  [I
   2:         16960        1899520  java.util.GregorianCalendar
   3:         16960        1628160  sun.util.calendar.Gregorian$Date
   4:         16965         950040  sun.util.calendar.ZoneInfo
   5:         16959         949704  java.util.Calendar$Builder

Calendar 对象很多。

2. 查看线程栈中,使用到Calendar的调用栈。可以 快速定位到具体的调用链

>jstack 46800

"main" #1 prio=5 os_prio=0 tid=0x0000000003217000 nid=0xe37c runnable [0x0000000002e5e000]
   java.lang.Thread.State: RUNNABLE
        at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2312)
        at java.util.Calendar.setTimeInMillis(Calendar.java:1804)
        at java.util.Calendar$Builder.build(Calendar.java:1508)
        at sun.util.locale.provider.CalendarProviderImpl.getInstance(CalendarProviderImpl.java:88)
        at java.util.Calendar.createCalendar(Calendar.java:1666)
        at java.util.Calendar.getInstance(Calendar.java:1613)

3. 再看代码,就定位到问题了。 |

4. 通过dump文件定位下问题

dump文件时,不加 live选型,把所有对象都dump下来;然后再加live选型,只dump存活对象;

对两个dump文件对比。

通过 java自带的 jvisualvm 查看

所有对象:

仅存活对象:

可以看出来,有这么多的对象 需要被回收。

也可以猜出出,是 死循环 在不断的 创建对象。

一开始 我以为 是 数据库查询的数据量太多导致 有大量对象被回收,如果真是数据库查询出的话,应该是直接 OOM,而不是频繁GC。

通过这个案例,可以看出,排查问题,需要熟练使用 堆相关命令、栈相关命令 结合使用。

可以用这个代码进行测试:

JVM设置:

  -Xms100m -Xmx100m -Xmn50m  -XX:+PrintGCDetails

代码:
  
    public static void main(String[] args) {
        while (true){
            Calendar calendar = Calendar.getInstance();
        }
    }

内存泄漏

内存泄漏,是指 内存 申请后,无法被释放。

另一个概念叫内存溢出,是指 申请的内存 不足以 存放 程序需要存放的数据。

内存溢出,会导致 不可预估的 结果;而内存泄漏,会导致 程序最终因没有内存而无法正常运行。

内存泄漏,不是 没有发生GC,不是 不回收内存,而是 GC 认为 这部分内存 仍然被使用,不应该被回收。 这就像,程序 使用完这部分内存后, 犯了一个错误,没有给GC 发出 回收信号,导致GC认为 这部分内存 仍然有用。 而程序 犯的这个错误,是需要 我们 找出来。


服务器内存占用过高

Java 服务启动后,并没有运行什么程序,内存占用却达到了 90%以上,什么原因?

一般我们说内存占用过高,就是指 服务器的物理机内存占用过高。而Java程序 只是 使用服务器内存的一个程序,还有其他的程序也占用内存。那么,Java 服务 所在的物理机内存占用过高,跟什么频繁GC/Full GC/程序变慢不一样。首先想到的是 JVM 吃了 很多 内存,有两种情况:

1.  初始分配内存过大

也就是 -Xms 设置的内存大小,是JVM 启动时 就要吃掉的内存,如果这个值设置很大,时 程序 运行过程中 没有创建太多的对象,那么 服务器内存 也是会被占用的。

2. JVM 回收内存回收不掉

比如:内存泄漏,导致内存不断的被占用,但是又回收不了,JVM占用的内存朝着 -Xmx 设置的值去发展,最终导致内存慢慢的被吃完了。



版权声明:本文为htydowd原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。