目录
一、JVM内存模型
内存相关参数
JVM内存模型如下图
二、栈空间
入栈出栈示意如下图所示。
与栈空间有关的两种异常如下。
Java线程的6种状态如下:
那么,继续思考如下两个问题。
三、堆空间
那么,内存如何设置呢?
对象进入老年代一般有以下4种可能:
四、垃圾回收相关参数
五、JIT编译器相关参数
六、性能诊断相关参数
七、其它常用参数
JVM(Java虚拟机)参数优化对于提高Java应用程序的性能至关重要。通过调整JVM参数,可以更好地控制内存使用、垃圾回收行为以及编译器设置等。
JVM参数优化主要从以下几个方面进行,下面针对其进行简单的聊一聊。
-Xms
-Xmn
-XX:MaxMetaspaceSize=
-XX:NewRatio=
-XX:SurvivorRatio=
一般业务开发需要重点关注的是堆空间和栈空间。对于CodeCache也可以适当关注,一般只要CodeCache没有用满是不会对性能有明显影响的。堆外内存在业务开发过程中使用的也很少,通常只有Netty等通信类组件会使用,出现问题的概率并不高,即使有问题,业务开发也很难做出优化,通常是某个低版本组件有bug,升级组件版本即可解决。
默认情况下每创建一个Java线程就会分配1MB的栈空间,可以通过Xss参数来调整线程栈空间大小。当物理内存较少时,可以适当调整Xss,一般256KB到512KB就足够了。那么创建同样数量的线程时,可以节约一部分内存资源,并将节省出来的内存给堆内存使用。
与栈有关的4个重要概念:栈帧、栈深、栈顶、栈底。我们知道栈是一种后进先出的数据结构,假设A方法调用了B方法,那么会先将A方法压入栈内,然后在调用B方法时,再将B方法压入栈内,如果B方法执行完成就执行出栈操作,回到A方法继续执行,当A方法也执行完成时,A方法出栈。这里每个被压入栈内的方法在数据结构上表现为一个栈帧,里面存放了方法的局部变量表、操作数栈、动态连接方法和返回地址等信息。而栈深表示的是当前有多少个方法被压入栈内,方法调用层级越多,栈深的值就越大。栈顶表示当前正在被执行的方法栈帧,也就是最后一个入栈的方法。栈底表示首个压入栈内的方法,一般是Thread.run方法,也就是线程刚被创建时调用的方法。
StackOverflowException,又叫栈溢出,通常使用了递归调用造成栈深过大,所有栈帧占用的内存总和超出了Xss的限制,就会抛出该异常。
OutOfMemoryException,通常在启动一个新线程时发生,如果应用有线程泄漏的问题,在流量高峰时未通过线程池合理地控制线程数,而是不断创建新线程来处理新请求,那么当物理内存不足以为新线程分配栈空间时,就会抛出该异常。
新建状态(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频率的目的。
长期存活的对象:经过多次YoungGC(默认15次)未回收掉的,会进入老年代
空间分配担保:在Young GC过程中,如果Sunivor区无法放下所有存活的年轻代对象,那么这些对象也会直接晋升至老年代。
动态对象年龄判定:如果Sunivor区中相同年龄所有对象的大小总和大于Sunivor区空间的一半,年龄大于或者等于该年龄的对象在MinorGC时将被复制到老年代。
大对象直接进入老年代:如果设置了MaxTenuringThreshold,那么占用内存超过MaxTenuringThreshold参数限制的新创建对象会直接在老年代分配。
-XX:+UseSerialGC:使用串行垃圾收集器,适用于小型应用。
-XX:+UseParallelGC:使用并行垃圾收集器,旨在提高吞吐量,适合多核CPU环境。
-XX:+UseConcMarkSweepGC 或 -XX:+UseG1GC:使用CMS或G1垃圾收集器,两者都设计用于减少应用程序的停顿时间。
-XX:MaxGCPauseMillis=
-XX:InitiatingHeapOccupancyPercent=
-XX:CompileThreshold=
-XX:ReservedCodeCacheSize=
-XX:+HeapDumpOnOutOfMemoryError:当JVM抛出OutOfMemoryError异常时,自动导出堆内存快照。
-XX:HeapDumpPath=
-XX:+PrintGCDetails 和 -XX:+PrintGCDateStamps:打印详细的GC日志信息,有助于分析GC的行为。
-XX:OnOutOfMemoryError="
-D
-server:启用服务器模式下的JVM,这通常意味着更积极的JIT编译策略。
阅读后若有收获,不吝关注,点赞,分享转发,留言评论等操作!!!