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()方法可以自救。

目录
相关文章
|
3月前
|
存储 监控 算法
jvm-性能调优(二)
jvm-性能调优(二)
|
5月前
|
Arthas 监控 Java
(十一)JVM成神路之性能调优篇:GC调优、Arthas工具详解及各场景下线上最佳配置推荐
“在当前的互联网开发模式下,系统访问量日涨、并发暴增、线上瓶颈等各种性能问题纷涌而至,性能优化成为了现时代开发过程中炙手可热的名词,无论是在开发、面试过程中,性能优化都是一个常谈常新的话题”。
468 3
|
5月前
|
监控 Java 测试技术
JVM 性能调优 及 为什么要减少 Full GC
JVM 性能调优 及 为什么要减少 Full GC
124 4
|
19天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
17天前
|
监控 Java 编译器
Java虚拟机调优实战指南####
本文深入探讨了Java虚拟机(JVM)的调优策略,旨在帮助开发者和系统管理员通过具体、实用的技巧提升Java应用的性能与稳定性。不同于传统摘要的概括性描述,本文摘要将直接列出五大核心调优要点,为读者提供快速预览: 1. **初始堆内存设置**:合理配置-Xms和-Xmx参数,避免频繁的内存分配与回收。 2. **垃圾收集器选择**:根据应用特性选择合适的GC策略,如G1 GC、ZGC等。 3. **线程优化**:调整线程栈大小及并发线程数,平衡资源利用率与响应速度。 4. **JIT编译器优化**:利用-XX:CompileThreshold等参数优化即时编译性能。 5. **监控与诊断工
|
28天前
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
2月前
|
Arthas 监控 数据可视化
JVM进阶调优系列(7)JVM调优监控必备命令、工具集合|实用干货
本文介绍了JVM调优监控命令及其应用,包括JDK自带工具如jps、jinfo、jstat、jstack、jmap、jhat等,以及第三方工具如Arthas、GCeasy、MAT、GCViewer等。通过这些工具,可以有效监控和优化JVM性能,解决内存泄漏、线程死锁等问题,提高系统稳定性。文章还提供了详细的命令示例和应用场景,帮助读者更好地理解和使用这些工具。
|
2月前
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
2月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
52 3
|
2月前
|
Java API 对象存储
JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?
本文详细解析了JVM类加载过程的关键步骤,包括加载验证、准备、解析和初始化等阶段,并介绍了元数据区、程序计数器、虚拟机栈、堆内存及本地方法栈的作用。通过本文,读者可以深入了解JVM的工作原理,理解类加载器的类型及其机制,并掌握类加载过程中各阶段的具体操作。