首页 > 编程知识 正文

JVM优化,JVM性能调优

时间:2023-05-04 15:36:50 阅读:23767 作者:919

一. JVM内存模型及垃圾收集算法

根据Java虚拟机的规范,JVM按如下方式划分内存:

新时代

Tenured (老年一代) ) ) ) ) )。

永久世代(Perm ) )。

其中,New和Tenured是堆内存,堆内存由JVM启动参数(-Xmx:3G )指定的内存分配。 Perm是直接分配虚拟机而不是堆内存,但可以使用- xx : permsize-xx 3360 maxperm size等参数调整大小。

年轻一代(New )年轻一代用于存储JVM刚刚分配的Java对象

老一代(Tenured )年轻一代中,经过垃圾回收而无法回收的对象将被复制到老一代

永久(Perm )永久世代存储Class、Method元信息,其大小关系到项目的规模、类、方法的量,通常设定为128M就足够了,设置原则上确保30%的空间。

年轻代又分为几个部分:

Eden:Eden用于存储JVM刚刚分配的对象

GC开始时,对象只存在于Eden区和名为“From”的Survivor区,Survivor区“To”为空。 紧接在GC之后,在Eden区,所有存活的对象都被复制到“至”,而在“从”区,存活的对象根据年龄值确定去向。 年龄达到一定值(可在-XX:MaxTenuringThreshold中设置)的对象将移动到年龄层代,未达到阈值的对象将复制到“到”区域。 经过这次的GC,Eden区和From区都空了。 此时,“从”和“到”交换他们的角色。 也就是说,新的“到”是上次GC前的“从”,新的“从”是上次GC前的“到”。 无论如何,都将确保名为To的Survivor区域是空闲的。 Minor GC重复此过程,直到“到”区域已填充,“到”区域已填充,然后将所有对象移动到年老的层代。

2 .垃圾回收算法

年轻代都基于标记-清除(复制)算法:

串行算法(单线程)、并行算法、并行算法和JVM根据机器的硬件配置为每个存储器代选择适当的回收算法。

年轻一代的垃圾回收动作什么时候执行?

年轻一代内存满的话,会诱发一次普通的GC,该GC只回收年轻一代。 需要强调的是,年轻一代满是伊甸园一代满,而Survivor满是不会诱发GC

当年老一代满的时候会发生全GC,全GC同时回收年轻一代、老一代

永久世代满后,还会发生全GC,导致Class、Method元信息的卸载

另一个问题是内存输出何时抛出,而不是内存耗尽时抛出

98%的JVM时间用于内存回收

每次回收的内存不到2%

满足这两个条件时,将触发内存输出执行。 这样可以在系统中提供很小的间隙,并执行Down之前的操作. 水平。

二.内存泄漏和解决方法

1 .系统崩溃前的一些现象:

回收垃圾的时间越来越长了

全GC的次数增加,最频繁地在不到1分钟内进行全GC

老一代的内存越来越大,在FullGC之后,内存不会释放给老一代

然后,系统将无法响应新请求,并逐渐达到内存错误输出阈值。

2 .生成堆的dump文件

从JMX的MBean生成当前的Heap信息。 大小为3G (堆的总体大小)的hprof文件,如果没有启动JMX,可以通过Java的jmap命令生成。

分析dump文件

让我们考虑一下如何打开这个3G堆信息文件。 很明显,典型的窗口系统没有那么大的内存,必须使用高配置的Linux。 当然,可以使用x窗口将Linux上的图形导入到窗口中。 要打开此文件,请考虑以下工具:

Visual VM虚拟机

JDK/在犹豫不决的长颈鹿目录中启动

以下是Visual GC查看各代回收时间、回收次数、类加载等具体信息。 也可以通过“工具”下载其他插件,如分析线程、跟踪等。

问)坍塌前回收垃圾的时间为什么会变长?

A:根据内存模型和垃圾收集算法将垃圾收集分为内存标记、清除和拷贝两部分。 标记部分只要内存大小一定,时间就不变。 改变的是复印部分。 每次垃圾回收都有无法回收的内存,因此复制量增加,时间延长。 因此,垃圾回收时间也是判断存储器泄漏的依据

问)为什么GC的次数在增加?

a )因此,内存积累,老一代内存逐渐耗尽,新对象分配没有更多空间,导致频繁的垃圾回收

Q:为什么老年人消耗越来越多的内存?

A:已从越来越多的副本复制到老一代,因为年轻一代的内存无法回收

三.绩效调整

3.JVM参数

JVM启动参数提供了几个与内存、垃圾回收相关的参数设置。 缺省情况下,如果未设置任何内容,JVM将正常工作,但某些配置良好的服务器和特定的APP应用程序需要仔细调整才能获得最佳性能。 通过设定我们想达到的目标:

GC的时间足够的小
GC的次数足够的少
发生Full GC的周期足够的长
前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。

(1)针对JVM堆的设置一般,可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
(2)年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小

(3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响

更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:(A)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 (B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间
(4)在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC ,默认为Serial收集

(5)线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。

(4)可以通过下面的参数打Heap Dump信息
-XX:HeapDumpPath
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:/usr/aaa/dump/heap_trace.txt
通过下面参数可以控制OutOfMemoryError时打印堆的信息
-XX:+HeapDumpOnOutOfMemoryError

参考:https://blog.csdn.net/chen77716/article/details/5695893

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。