jvm调优

简介: jvm调优


2022/03/05

csdn 链接
https://blog.csdn.net/love_yr/article/details/121800595(有图片和目录)
JVM学什么?
(1)源码到类文件
(2)类文件到JVM
(3)JVM各种折腾[内部结构、执行方式、垃圾回收、本地调用等]
什么是JVM优化(对JVM优化的理解)
根据需求进行JVM规划和预调优

优化运行JVM运行环境(慢,卡顿)

解决JVM运行过程中出现的各种问题(OOM)

JVM的性能优化可以分为代码层面和非代码层面。
在代码层面,大家可以结合字节码指令进行优化,比如一个循环语句,可以将循环不相关的代码提
取到循环体之外,这样在字节码层面就不需要重复执行这些代码了。
在非代码层面,一般情况可以从内存、gc以及cpu占用率等方面进行优化。
VM调优是一个漫长和复杂的过程,而在很多情况下,JVM是不需要优化的,因为JVM本身
已经做了很多的内部优化操作。
内存溢出分类
栈内存溢出         java.lang.StackOverflowError

堆内存溢出        java.lang.OutOfMemoryError: heap

方法区内存溢出        java.lang.OutOfMemoryError: Metaspace

直接内存溢出

如何分析堆内存
怎么回答

1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件

2:很多服务器备份(高可用),隔离这台服务器,使用其他的服务器

在线分析(2种):
jmap

jmap - histo 4655 | head -20,查找有多少对象产生

arthas   (线上分析)

下载arthas jar包运行,绑定到想检测的进程   

jvm观察jvm信息

thread定位线程问题

dashboard 观察系统情况(查看线程占用,内存占用)

jad反编译动态代理生成类的问题定位

第三方的类(观察代码)

版本问题(确定自己最新提交的版本是不是被使用)

redefine 热替换目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性m() -> mm()

sc - search class

watch - watch method

jconsole       

远程监控(JMX协议),需要在程序启动时指定参数,一般只在测试时候用,在线用严重影响主程序效率

jvisualVm  同jconsole       

离线分析(生成dump堆转储文件)
生成dump堆转储文件的过程占用系统资源,造成严重卡顿,不建议生成堆转储分析

获得堆转储文件的几种方式
1.堆内存溢出时自动获取

java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mg.jvm.gc.T15_FullGC_Problem01

2.使用jmap命令

jmap -dump:format=b,file=xxx pid

3.使用arthas的heapdump命令

分析堆转储文件的几种工具
1.MAT

2.jhat                jhat -J-mx512M xxx.dump(就指定让其使用512M内存)

3.jvisualVm      java自带,打开后导入堆转储文件

如何选择垃圾回收器
关注的两个指标
吞吐量和停顿时间
停顿时间->垃圾收集器 进行 垃圾回收终端应用执行响应的时间
吞吐量->运行用户代码时间/(运行用户代码时间+垃圾收集时间)
选择收集器时的考虑
优先调整堆的大小让服务器自己来选择
如果内存小于100M,使用串行收集器
如果是单核,并且没有停顿时间要求,使用串行或JVM自己选
如果允许停顿时间超过1秒,选择并行或JVM自己选
如果响应时间最重要,并且不能超过1秒,使用并发收集器
何时使用并发收集器g1
(java1.8及以上,内存6G以上,优先考虑G1)

(1)50%以上的堆被存活对象占用
(2)对象分配和晋升的速度变化非常大
(3)垃圾回收时间比较长
G1详细介绍
G1内存分配策略

将内存分成一个一个的region,且不要求各部分是连续的。
每个Region的大小在JVM启动时就确定,JVM通常生成2000个左右的heap区, 根据堆内存的总大小,区的size范围为1-32Mb,一般4M.

region类型

三种常见: Eden, Survivor, 或 old generation(老年代)区  HumongousHumongous
巨无霸区:保存比标准region区大50%及以上的对象,存储在一组连续的区中.转移会影响GC效率,标记阶段发现巨型对象不再存活时,会被直接回收。
未使用区:未被使用的region
特别说明:某个region的类型不是固定的,比如一次ygc过后,原来的Eden的分区就会变成空闲的可用分区,随后也可能被用作分配巨型对象

基本概念
cardTable(属于堆的概念,不单属于g1)
基于卡表(Card Table)的设计,通常将堆空间划分为一系列2次幂大小的卡页(Card Page)。

卡表(Card Table),用于标记卡页的状态,每个卡表项对应一个卡页。

HotSpot JVM的卡页(Card Page)大小为512字节,卡表(Card Table)被实现为一个简单的字节数组,即卡表的每个标记项为1个字节。

当对一个对象引用进行写操作时(对象引用改变),写屏障逻辑将会标记对象所在的卡页为dirty。

cardTable带来的2个问题
1.无条件写屏障带来的性能开销

每次对引用的更新,无论是否更新了老年代对新生代对象的引用,都会进行一次写屏障操作。显然,这会增加一些额外的开销。但是,与YGC时扫描整个老年代相比较,这个开销就低得多了。

不过,在高并发环境下,写屏障又带来了虚共享(false sharing)问题。

2.高并发下虚共享带来的性能开销

在高并发情况下,频繁的写屏障很容易发生虚共享(false sharing),从而带来性能开销。

假设CPU缓存行大小为64字节,由于一个卡表项占1个字节,这意味着,64个卡表项将共享同一个缓存行。

HotSpot每个卡页为512字节,那么一个缓存行将对应64个卡页一共64*512=32KB。

如果不同线程对对象引用的更新操作,恰好位于同一个32KB区域内,这将导致同时更新卡表的同一个缓存行,从而造成缓存行的写回、无效化或者同步操作,间接影响程序性能。

一个简单的解决方案,就是不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表项未被标记过才将其标记为dirty。

这就是JDK 7中引入的解决方法,引入了一个新的JVM参数-XX:+UseCondCardMark,在执行写屏障之前,先简单的做一下判断。如果卡页已被标识过,则不再进行标识。

待收集集合,CSets
由G1MixedGCLiveThresholdPercent参数控制的,old代分区中的存活对象比,达到阀值时,说明该region可以被回收的对象比较多,这个old分区会被放入CSet,等待被GC。
Collection Sets,有垃圾需要被回收的region的集合。CSet中可能存放着各个分代的Region。CSet中的存活对象会在gc中被移动(复制)。GC后CSet中的region会成为可用分区。
策略:由G1MixedGCLiveThresholdPercent参数控制的,old代分区中的存活对象比,达到阀值时,这个old分区会被放入CSet,后面会被执行回收整理。

已记忆集合,RSets

RememberedSets,存储着其他分区中的对象对本分区对象的引用,每个分区有且只有一个RSet。用于提高GC效率。
YGC时,GC root主要是两类:栈空间和老年代分区到新生代分区的引用关系。所以记录老年代分区对新生代分区的引用
Mixed GC时,由于仅回收部分老年代分区,老年代分区之间的引用关系也将被使用。所以记录老年代分区之间的引用
因此,我们仅需要记录两种引用关系:老年代分区引用新生代分区,老年代分区之间的引用。
因为每次GC都会扫描所有young区对象,所以RSet只有在扫描old引用young,old引用old时会被使用。
 

G1的GC类型
1)Ygc:仅处理年轻代region
2)MixedGc:包含所有年轻代以及部分老年代Region。
3)FullGc:全堆扫描,每个Region

MixGc过程:

1)标记GCroots,一般直接复用YoungGC中的结果
2)根分区扫描(RootRegionScan)。这个阶段GC的线程可以和应用线程并发运行。其主要扫描初始标记以及之前YoungGC对象转移到的Survivor分区,并标记Survivor区中引用的对象。所以此阶段的Survivor分区也叫根分区(RootRegion)
3)并发标记(ConcurrentMark)。会并发标记所有非完全空闲的分区的存活对象,也即使用了SATB算法,标记各个分区。
4)最终标记(Remark)。主要处理SATB缓冲区,以及并发标记阶段未标记到的漏网之鱼(存活对象),会STW,可以参考上文的SATB处理。
5)清除阶段(Clean UP)。整理堆分区,调整相应的RSet(比如如果其中记录的Card中的对象都被回收,则这个卡片的也会从RSet中移除),如果识别到了完全空的分区,则会清理这个分区的RSet。这个过程会STW。
6)对存活对象进行转移(复制算法),转移到其他可用分区,所以当前的分区就变成了新的可用分区。复制转移主要是为了解决分区内的碎片问题。
 

G1的FullGc

G1在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发FullGC。
开始版本FullGC使用的是stop the world的单线程的Serial Old模式。
JDK10以后,Full GC已经是并行运行,在很多场景下,其表现还略优于 Parallel GC 的并行 Full GC 实现。
但是仍然要避免fgc。

G1调优的一些考虑
1.不要设置年轻代的大小,默认5%到60%,是G1动态调整暂停stw时间的依据

2 设置 XX:MaxGCPauseMillis=
其值不应该使用平均响应时间,应该考虑使用目标时间的90%或者更小作为响应时间指标. 即90%的用户(客户端/?)请求响应时间不会超过预设的目标值;

3 转移失败
survivors 或 promoted objects 进行GC时如果JVM的heap区不足就会发生提升失败(promotion failure). 堆内存不能继续
扩充,因为已经达到最大值了. 当使用 -XX:+PrintGCDetails 时将会在GC日志中显示 to-space overflow (to-空间溢出)。该操作很昂贵,原因如下:
1)GC仍继续所以空间必须被释放. 
2)拷贝失败的对象必须被放到正确的位置(tenured in place). 
3)CSet指向区域中的任何 RSets 更新都必须重新生成(regenerated). 

避免转移失败的方法:
1)增加保留内存大小, 其默认值是 10;G1保留内存大小,非必须不会使用保留内存;即增大-XX:G1ReservePercent=n
2)更早启动标记周期(marking cycle).即InitiatingHeapOccupancyPercent设置的小一点??
3)增加标记线程(marking threads)的数量. 合理设置-XX:ConcGCThreads=n

4 新生代优化-避免短生命对象进入老年代
预估每次Minor GC后存活下来对象的大小,合理的设置Survivor区,同时考虑高峰期间时,动态年龄判断条件的影响,不要让这种短生命周期对象侥幸逃脱进入老年代

G1相关的参数

-XX:MaxGCPauseMillis=200 - 设置最大GC停顿时间指标,JVM会尽力实现,但不保证. 默认值为200毫秒.
-XX:InitiatingHeapOccupancyPercent=45 - 如果老年代占据了堆内存的45%的时候,此时会触发一次mixGc。值为0则表示“一直执行GC循环)'. 默认值为45。
-XX:G1MixedGCLiveThresholdPercent:默认值是85%,确定要回收的Region的时候,必须是存活对象低于85%的Region才可以回收。
-XX:G1ReservePercent=n 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:+UseG1GC - 让 JVM使用G1垃圾收集器, jdk9被设为默认垃圾收集器;所以如果你的版本比较新则不再需要使用该参数
-XX:MetaspaceSize=256M 元空间,默认20M,确实有点小。
-XX:MaxMetaspaceSize=512M 最大元空间

下面参数不建议修改
-XX:G1NewSizePercent=5    设置年轻代占整个堆的最小百分比,默认值是堆的5%。需要开启-XX:UnlockExperimentalVMOptions
-XX:G1MaxNewSizePercent=60    设置年轻代占整个堆的最大百分比,默认值是堆的60%。
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n 年轻代提升到年老代的最大临界值. 默认值为 15.
-XX:G1HeapRegionSize=n region大小  默认值将根据 heap size 算出最优解;1M-32M
-XX:G1MixedGCCountTarget mixed回收执行次数,默认回收次数8。
-XX:G1HeapWastePercent,默认值是5%,就是说空出来的区域大于整个堆的5%,即使未达到回收次数,也会立即停止混合回收了。
如:默认回收次数是8次,但是可能到了4次,发现空闲Region大于整个堆的5%,就不会再进行后续回收了。
 

JVM优化常见问题及工具使用
1.cpu占用过高的解决方法

  1. 使用nohup java 命令运行后台运行java程序
  2. 使用top命令查看各进程对本地内存和cpu的占用情况

3.  找到占用cpu高的进程的pid

4.  使用ps H -eo pid ,tid,%cpu |grep pid 找到这个进程下哪个线程占用cpu的比例较高

         -eo(输出哪些感兴趣的内容),这里找到的线程id是十进制的,转换成十六进制(7f99) 

或者使用top -Hp 命令也能查看进程下id的占用情况(图二)​​​​​​

 

  1. 使用jstack  进程id 查看进程下线程的详细情况

6.  根据十六进制的线程id找到对应的线程(看nid)

 

         找到问题了,原来是这个类的第八行是一个while true的死循环

2.程序迟迟得不到响应结果,也不报错
(可能发生了线程死锁)

  1. 使用nohup java运行java程序(同上)
  2. 使用top命令找到有问题的进程id(同上)

3.使用jstack查看进程里线程的详细信息,在信息的最下方有死锁的信息

found one java-level deadlock    waiting to lock XXXX

堆内存诊断(OOM)

 jamp -heap 进程id  (抓取堆内存快照,堆内存各个区域的使用情况)

 jmap - histo 4655 | head -20(查找有多少对象产生,只看前20行数据,有排序)

通过查看这个类一共产生了多少个对象,占用内存多少来判断应该是哪个类出现了问题

3、垃圾收集发生的时间是什么时候?
    Full GC = Major GC + Minor GC +Metaspace Gc;
    (1)Eden区或者S区的空间不够用的时候 ---->MinorGC
    (2)老年代的空间不够用的时候  ----->Major GC  出发MajorGC往往会伴随着Full GC    
    (3)方法区不够用了也会出发GC
    (4)System.gc()方法调用的时候,但是这只是向虚拟机发出一个 指令。但是什么时候发生回收,还不能确定。只能让虚拟机自己决定。

4、如果Full GC频繁怎么办?或者如何减少Full GC的次数?

    适当的将Yong区增大。 设置YOng和Old的占比。尽可能让对象在Yong区进行回收    

5、如果GC的次数频繁,会怎么办?
    首先拿到gc的日志。然后通过工具进行分析日志。如果是堆内存空间不够用,则要适当的增加堆内存。也许是选择的垃圾回收器不太合适。 如果用的G1垃圾回收器,查看设置的停顿时间是否太严格了。或者堆内存使用比例是不是了。

6、如果cpu飙升怎么办?
    使用top命令查看时哪个进程占用cpu比较大
    (1)因为并发量太大了。导致一些占用cpu的运算一直处于运算中
            解决方案:搭建集群。增加MQ延缓代码的处理
    (2)查看线程是否存在死循环

7、如果发生了OOM怎么办?
    通过dump文件查看oom。分析dump文件  工具MAT
 
内存泄漏和内存溢出有区别嘛?
    内存泄漏是指:哪些对象没法进行回收,持续的占用内存空间。
    内存溢出:OMM是指内存没法装下对象
 
方法去中的回收主要是什么内容?
    没有用的类的信息、常量、静态变量
    
再问:类的信息什么时候被回收?
        (1)堆中不再有该对象
        (2)加载该类的classCloader已经被回收。因为classLoader是可以作为GC Root的
        (3)java.lang.Class对象也不再有任何地方引用了

8、不可达的对象一定会被回收嘛?
    finalize()方法可以自救。

目录
相关文章
|
16天前
|
存储 监控 算法
jvm-性能调优(二)
jvm-性能调优(二)
|
3月前
|
Arthas 监控 Java
(十一)JVM成神路之性能调优篇:GC调优、Arthas工具详解及各场景下线上最佳配置推荐
“在当前的互联网开发模式下,系统访问量日涨、并发暴增、线上瓶颈等各种性能问题纷涌而至,性能优化成为了现时代开发过程中炙手可热的名词,无论是在开发、面试过程中,性能优化都是一个常谈常新的话题”。
245 3
|
3月前
|
监控 Java 测试技术
JVM 性能调优 及 为什么要减少 Full GC
JVM 性能调优 及 为什么要减少 Full GC
94 4
|
4月前
|
监控 Java 调度
探秘Java虚拟机(JVM)性能调优:技术要点与实战策略
【6月更文挑战第30天】**探索JVM性能调优:**关注堆内存配置(Xms, Xmx, XX:NewRatio, XX:SurvivorRatio),选择适合的垃圾收集器(如Parallel, CMS, G1),利用jstat, jmap等工具诊断,解决Full GC问题,实战中结合MAT分析内存泄露。调优是平衡内存占用、延迟和吞吐量的艺术,借助VisualVM等工具提升系统在高负载下的稳定性与效率。
91 1
|
4月前
|
监控 Java 测试技术
Java中的JVM调优技巧
Java中的JVM调优技巧
|
17天前
|
Kubernetes Java 编译器
解锁极致性能:Quarkus如何让JVM应用调优变得前所未有的简单与高效!
Quarkus是一款专为GraalVM和OpenJDK设计的Kubernetes Native Java框架,采用AOT编译技术将Java应用转化为本地代码,大幅提升启动速度与运行效率。它简化了性能调优流程,如自动优化垃圾回收、类加载、内存管理及线程管理等,使开发者无需深入理解JVM细节即可轻松提升应用性能。与传统JVM应用相比,Quarkus显著降低了性能调优的复杂度。
55 2
|
19小时前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
3月前
|
运维 Java Linux
(九)JVM成神路之性能调优、GC调试、各内存区、Linux参数大全及实用小技巧
本章节主要用于补齐之前GC篇章以及JVM运行时数据区的一些JVM参数,更多的作用也可以看作是JVM的参数列表大全。对于开发者而言,能够控制JVM的部分也就只有启动参数了,同时,对于JVM的性能调优而言,JVM的参数也是基础。
|
3月前
|
缓存 监控 Java
Java虚拟机(JVM)性能调优实战指南
在追求软件开发卓越的征途中,Java虚拟机(JVM)性能调优是一个不可或缺的环节。本文将通过具体的数据和案例,深入探讨JVM性能调优的理论基础与实践技巧,旨在为广大Java开发者提供一套系统化的性能优化方案。文章首先剖析了JVM内存管理机制的工作原理,然后通过对比分析不同垃圾收集器的适用场景及性能表现,为读者揭示了选择合适垃圾回收策略的数据支持。接下来,结合线程管理和JIT编译优化等高级话题,文章详细阐述了如何利用现代JVM提供的丰富工具进行问题诊断和性能监控。最后,通过实际案例分析,展示了性能调优过程中可能遇到的挑战及应对策略,确保读者能够将理论运用于实践,有效提升Java应用的性能。 【
168 10
|
3月前
|
监控 算法 Java
深入理解Java虚拟机:JVM调优的实用策略
在Java应用开发中,性能优化常常成为提升系统响应速度和处理能力的关键。本文将探讨Java虚拟机(JVM)调优的核心概念,包括垃圾回收、内存管理和编译器优化等方面,并提供一系列经过验证的调优技巧。通过这些实践指导,开发人员可以有效减少延迟,提高吞吐量,确保应用稳定运行。 【7月更文挑战第16天】