GC事件
JVM的垃圾回收GC事件可以分为以下几类:
- Minor GC(新生代GC):针对新生代(Young Generation)的垃圾回收事件。在新生代中,通常采用复制算法进行垃圾回收,将存活的对象复制到另一个区域,同时回收无用的对象。Minor GC通常发生频率较高,但回收的对象数量较少。当JVM无法为新对象分配内存空间时总会触发Minor GC)
- Major GC(老年代GC):针对老年代(Old Generation)的垃圾回收事件。在老年代中,通常采用标记-清除-整理算法进行垃圾回收,首先标记出存活的对象,然后清除无用的对象,最后进行内存整理。Major GC通常发生频率较低,但回收的对象数量较多。
- Full GC(全局GC):对整个堆内存进行垃圾回收的事件。Full GC包括对新生代和老年代的垃圾回收,通常发生在新生代和老年代都满了的情况下,或者由于系统调用触发。Full GC的开销较大,会导致应用程序的停顿时间较长。
- Concurrent GC(并发GC):在应用程序运行的同时进行垃圾回收的事件。并发GC的目标是尽量减少应用程序的停顿时间,通过与应用程序并发执行,提高系统的吞吐量。常见的并发GC算法有CMS(Concurrent Mark Sweep)和G1(Garbage First)。
JVM 中最重要的一部分就是堆空间了,基本上大多数的线上 JVM 问题都是因为堆空间造成的 OutOfMemoryError。因此掌握 JVM 关于堆空间的参数配置对于排查线上问题非常重要。
tips:本文所有配置,如无特别说明,均基于JDK1.8。
堆配置
我们使用 -Xms 设置堆的初始空间大小,使用 -Xmx 设置堆的最大空间大小。
java -Xms20m -Xmx30m GCDemo
在上面的命令中,我们设置 JVM 的初始堆大小为 20M,最大堆空间为 30M。
年轻代
在 JDK1.8 中,堆分为年轻代和老年代。JVM 提供了参数 -Xmn 来设置年轻代内存的大小,但没有提供参数设置老年代的大小。但其实老年代的大小就等于堆大小减去年轻代大小。
java -Xms20m -Xmn10M GCDemo
上面的命令中,我们设置 JVM 堆初始大小为20M。其中年轻代的大小为 10M,那么剩下的就是老年代的大小,有 10M了。 我们可以给上述命令加上-XX:+PrintGCDetails
参数来查看内存区域的分配信息。
如上图所示,我们可以看到老年代的大小为 10M。
Eden区
在年轻代中,分为三个区域,分别是:eden 空间、from 空间、to 空间。如果要设置这部分的大小,那么就使用 -XX:SurvivorRatio 这个参数,该参数设置 eden / from 空间的比例关系,该参数的公式如下:
-XX:SurvivorRatio = eden/from = eden/to
例如我们的年轻代有 10 M,而我们设置 -XX:SurvivorRatio 参数为 2。也就是说 eden / from = eden / to = 2
。这里教一个快速计算的方法,我们假设 eden = 2,那么 from = 1,to = 1,那么 eden + from + to = 10M。这样就可以算出每一份大小是 10/4 = 2.5M。所以 Eden 区 = 2.5 * 2 = 5M,from 区是 2.5 M,to 区是 2.5 M。
下面我们运行下命令来验证一下。
java -Xms20m -Xmn10M -XX:SurvivorRatio=2 -XX:+PrintGCDetails GCDemo
在上面的启动参数中,我们设置堆初始大小为 20M,年轻代大小为 10M,年轻代的 SurvivorRatio 比例为 2。那么最终分配的结果将会是:年轻代 10M,其中 Eden 区 5M、From 区 2.5M、To 区 2.5 M,老年代 10M。
从上图可以看到:eden 空间是 5120 K,from 和 to 空间是 2560 K。
上图还有一个细节,即 PSYoungGen 这里的 total 只有 7680K,难道年轻代只有 7.5M 的内存吗?为什么不是 10M 呢?其实是因为这里的 total 指的是可用内存,from space 和 to space 两个区域,同一时间只有一个区域是可以用的。所以可用内存是 5120 + 2560 = 7680。
jvm参数
- -Xms
堆最小值
- -Xmx
堆最大堆值。-Xms与-Xmx 的单位默认字节都是以k、m做单位的。通常这两个配置参数相等,避免每次空间不足,动态扩容带来的影响。
- -Xmn
新生代大小
- -Xss
每个线程池的堆栈大小。在jdk5以上的版本,每个线程堆栈大小为1m,jdk5以前的版本是每个线程池大小为256k。一般在相同物理内存下,如果减少-xss值会产生更大的线程数,但不同的操作系统对进程内线程数是有限制的,是不能无限生成。
- -XX:NewRatio
设置新生代与老年代比值,-XX:NewRatio=4 表示新生代与老年代所占比例为1:4 ,新生代占比整个堆的五分之一。如果设置了-Xmn的情况下,该参数是不需要在设置的。
- -XX:PermSize
设置持久代初始值,默认是物理内存的六十四分之一
- -XX:MaxPermSize
设置持久代最大值,默认是物理内存的四分之一
- -XX:MaxTenuringThreshold
新生代中对象存活次数,默认15。(若对象在eden区,经历一次MinorGC后还活着,则被移动到Survior区,年龄加1。以后,对象每次经历MinorGC,年龄都加1。达到阀值,则移入老年代)
- -XX:SurvivorRatio
Eden区与Subrvivor区大小的比值,如果设置为8,两个Subrvivor区与一个Eden区的比值为2:8,一个Survivor区占整个新生代的十分之一
- -XX:+UseFastAccessorMethods
原始类型快速优化
- -XX:+AggressiveOpts
编译速度加快
- -XX:PretenureSizeThreshold
对象超过多大值时直接在老年代中分配
说明: 整个堆大小的计算公式: JVM 堆大小 = 年轻代大小+年老代大小+持久代大小。
增大新生代大小就会减少对应的年老代大小,设置-Xmn值对系统性能影响较大,所以如果设置新生代大小的调整,则需要严格的测试调整。
而新生代是用来存放新创建的对象,大小是随着堆大小增大和减少而有相应的变化,默认值是保持堆大小的十五分之一,-Xmn参数就是设置新生代的大小,也可以通过-XX:NewRatio来设置新生代与年老代的比例,java 官方推荐配置为3:8。
新生代的特点就是内存中的对象更新速度快,在短时间内容易产生大量的无用对象,如果在这个参数时就需要考虑垃圾回收器设置参数也需要调整。推荐使用: 复制清除算法和并行收集器进行垃圾回收,而新生代的垃圾回收叫做初级回收。
StackOverflowError和OutOfMemoryException。当线程中的请求的栈的深度大于最大可用深度,就会抛出前者;若内存空间不够,无法创建新的线程,则会抛出后者。栈的大小直接决定了函数的调用最大深度,栈越大,函数嵌套可调用次数就越多。
经验 :
- Xmn用于设置新生代的大小。过小会增加Minor GC频率,过大会减小老年代的大小。一般设为整个堆空间的1/4或1/3.
- XX:SurvivorRatio用于设置新生代中survivor空间(from/to)和eden空间的大小比例; XX:TargetSurvivorRatio表示,当经历Minor GC后,survivor空间占有量(百分比)超过它的时候,就会压缩进入老年代(当然,如果survivor空间不够,则直接进入老年代)。默认值为50%。
- 为了性能考虑,一开始尽量将新生代对象留在新生代,避免新生的大对象直接进入老年代。因为新生对象大部分都是短期的,这就造成了老年代的内存浪费,并且回收代价也高(Full GC发生在老年代和方法区Perm).
- 当Xms=Xmx,可以使得堆相对稳定,避免不停震荡
- 一般来说,MaxPermSize设为64MB可以满足绝大多数的应用了。若依然出现方法区溢出,则可以设为128MB。若128MB还不能满足需求,那么就应该考虑程序优化了,减少动态类的产生。