性能调优JVM参数优化方案有哪些

目录

一、JVM内存模型

内存相关参数

JVM内存模型如下图

二、栈空间

入栈出栈示意如下图所示。

与栈空间有关的两种异常如下。

Java线程的6种状态如下:

那么,继续思考如下两个问题。

三、堆空间

那么,内存如何设置呢?

对象进入老年代一般有以下4种可能:

四、垃圾回收相关参数

五、JIT编译器相关参数

六、性能诊断相关参数

七、其它常用参数


JVM(Java虚拟机)参数优化对于提高Java应用程序的性能至关重要。通过调整JVM参数,可以更好地控制内存使用、垃圾回收行为以及编译器设置等。

JVM参数优化主要从以下几个方面进行,下面针对其进行简单的聊一聊。

一、JVM内存模型

内存相关参数

-Xms 和 -Xmx分别设置JVM启动时的初始堆内存大小和最大堆内存大小。合理的设置可以帮助减少因堆内存不足导致的垃圾回收频率。

-Xmn设置新生代的大小。这个参数对于新生代垃圾回收器尤为重要。

-XX:MaxMetaspaceSize=设置元空间的最大大小,元空间用来存放类的结构信息。

-XX:NewRatio=设置年轻代(新生代)和老年代之间的比例。例如,设置为2表示老年代与年轻代的比为2:1。

-XX:SurvivorRatio=设置Eden空间与一个Survivor空间的比例。比如设置为8,则Eden:S0 = 8:1

JVM内存模型如下图

性能调优JVM参数优化方案有哪些_第1张图片

一般业务开发需要重点关注的是堆空间和栈空间。对于CodeCache也可以适当关注,一般只要CodeCache没有用满是不会对性能有明显影响的。堆外内存在业务开发过程中使用的也很少,通常只有Netty等通信类组件会使用,出现问题的概率并不高,即使有问题,业务开发也很难做出优化,通常是某个低版本组件有bug,升级组件版本即可解决。

二、栈空间

默认情况下每创建一个Java线程就会分配1MB的栈空间,可以通过Xss参数来调整线程栈空间大小。当物理内存较少时,可以适当调整Xss,一般256KB到512KB就足够了。那么创建同样数量的线程时,可以节约一部分内存资源,并将节省出来的内存给堆内存使用

与栈有关的4个重要概念:栈帧、栈深、栈顶、栈底。我们知道栈是一种后进先出的数据结构,假设A方法调用了B方法,那么会先将A方法压入栈内,然后在调用B方法时,再将B方法压入栈内,如果B方法执行完成就执行出栈操作,回到A方法继续执行,当A方法也执行完成时,A方法出栈。这里每个被压入栈内的方法在数据结构上表现为一个栈帧,里面存放了方法的局部变量表、操作数栈、动态连接方法和返回地址等信息。而栈深表示的是当前有多少个方法被压入栈内,方法调用层级越多,栈深的值就越大。栈顶表示当前正在被执行的方法栈帧,也就是最后一个入栈的方法。栈底表示首个压入栈内的方法,一般是Thread.run方法,也就是线程刚被创建时调用的方法。

入栈出栈示意如下图所示。

性能调优JVM参数优化方案有哪些_第2张图片

与栈空间有关的两种异常如下。

StackOverflowException,又叫栈溢出,通常使用了递归调用造成栈深过大,所有栈帧占用的内存总和超出了Xss的限制,就会抛出该异常。

OutOfMemoryException,通常在启动一个新线程时发生,如果应用有线程泄漏的问题,在流量高峰时未通过线程池合理地控制线程数,而是不断创建新线程来处理新请求,那么当物理内存不足以为新线程分配栈空间时,就会抛出该异常。

Java线程的6种状态如下:

新建状态(New):使用new关键字创建一个Thread对象,刚刚创建出的这个线程就处于新建状态。

在这个状态的线程没有与操作系统真正的线程产生关联,仅仅是一个Java对象。

可运行状态(Runnable:正在运行的线程,只有处于可运行状态的线程才会得到CPU资源。

阻塞状态(Blocked):在可运行阶段争抢锁失败的线程就会从可运行状态转变为阻塞状态。

等待状态(Waiting):可运行状态争抢锁成功,但是资源不满足,主动放弃锁(调用wait0方法),条件满足后再恢复可运行状态(调用notiy0方法)。

有时限等待状态(Timed_Waiting):类似于等待状态,不过区别在于有时限等待状态有一个等待的时间,到达等待时间或者调用notiy0方法,都能恢复为可运行状态。

终结状态(Terminated):代码全部执行完毕后,会进入终结状态,释放所有的资源。

那么,继续思考如下两个问题。

发生高CPU消耗问题时,会通过Jstack命令去抓取一个线程Dump文件,里面会显示抓取Dump瞬间的所有线程的栈信息,此时应该关注栈顶还是栈底?重点应该关注哪些状态的线程?

如果有大量线程的状态都是等待状态,有些线程栈深是10左右,有些线程栈深是100左右,我们应该重点关注哪类线程,为什么?

三、堆空间

以JDK1.8为例,堆空间分为年轻代、老年代、元空间(JDK1.8以下版本称为持久代),其中年轻代又可以细分为Eden区和Survivor区。GC原理是一块很庞大的知识,学习资料也很丰富,感兴趣的读者可以去查阅相关学习资料,在此不过多展开,大家只需知道程序中会用new关键字创建一个引用类型的对象,并在堆空间中分配一块内存来存放该对象即可。

那么,内存如何设置呢?

通常建议将Xmx(最大堆)和Xms(最小堆)设置成一样大,避免频繁扩容和GC释放堆内存造成系统开销。但如果Xms设置得足够大,在触发GC时,如果大部分对象已经成了垃圾对象,因此垃圾回收效果比较好的话,也可以考虑将Xmx设置得比Xms更大。此时由于垃圾回收效果良好,JVM通常不会动态调整堆内存大小,而将Xmx设置得更大,可以在预期外的高峰流量涌入时为应用提供更充足的堆内存空间,降低GC的频率。

Xmn(年轻代)默认是Java堆的三分之一,也可以按需调整。例如程序运行过程中会频繁创建一些生命0点性调代率要为1周期较短的对象,但每次Young GC后,发现晋升入老年代的对象超出你的预期,这可能是年轻代分配过小导致的。假设每秒生成100MB的短命对象,平均生命周期为15秒,年轻代大小是100MB,那么YoungGC发生的频率就是1秒/次,此时大部分对象还处于存活状态,因此回收过后进入Sunivor区,甚至直接晋升至老年代的对象就会很多。此时可以适当调大年轻代的比例,让对象尽可能在年轻代被回收掉,降低晋升至老年代的概率,也就可以达到降低FuIl GC频率的目的。

对象进入老年代一般有以下4种可能:

长期存活的对象:经过多次YoungGC(默认15次)未回收掉的,会进入老年代

空间分配担保:在Young GC过程中,如果Sunivor区无法放下所有存活的年轻代对象,那么这些对象也会直接晋升至老年代。

动态对象年龄判定:如果Sunivor区中相同年龄所有对象的大小总和大于Sunivor区空间的一半,年龄大于或者等于该年龄的对象在MinorGC时将被复制到老年代。

大对象直接进入老年代:如果设置了MaxTenuringThreshold,那么占用内存超过MaxTenuringThreshold参数限制的新创建对象会直接在老年代分配。

四、垃圾回收相关参数

-XX:+UseSerialGC:使用串行垃圾收集器,适用于小型应用。

-XX:+UseParallelGC:使用并行垃圾收集器,旨在提高吞吐量,适合多核CPU环境。

-XX:+UseConcMarkSweepGC 或 -XX:+UseG1GC:使用CMS或G1垃圾收集器,两者都设计用于减少应用程序的停顿时间。

-XX:MaxGCPauseMillis=设置垃圾收集的最大暂停目标时间,只对G1收集器有效。

-XX:InitiatingHeapOccupancyPercent=设置触发并发GC周期的堆占用百分比,适用于G1收集器。

五、JIT编译器相关参数

-XX:CompileThreshold=设置方法需要被执行多少次之后才会被编译成本地代码。

-XX:ReservedCodeCacheSize=设置JIT编译器使用的代码缓存大小。

六、性能诊断相关参数

-XX:+HeapDumpOnOutOfMemoryError:当JVM抛出OutOfMemoryError异常时,自动导出堆内存快照。

-XX:HeapDumpPath=指定堆内存快照文件的输出路径。

-XX:+PrintGCDetails 和 -XX:+PrintGCDateStamps:打印详细的GC日志信息,有助于分析GC的行为。

-XX:OnOutOfMemoryError="":当发生OutOfMemoryError时执行特定命令,如发送邮件通知等。

七、其它常用参数

-D=设置系统属性。

-server:启用服务器模式下的JVM,这通常意味着更积极的JIT编译策略。

阅读后若有收获,不吝关注,点赞,分享转发,留言评论等操作!!!

你可能感兴趣的:(漫谈测试,jvm,性能优化)