JVM 内存分配策略

JVM 堆内存组成

  Java堆由 Perm区 和 Heap区 组成,Heap区 由 Old区 和 New区(也叫Young区)组成,New区 由 Eden区、From区 和 To区(Survivor)组成。见下图 Heap 区示例:

  

  Eden区用于存放新生成的对象。Eden中的对象生命不会超过一次 Minor GC。
  Survivor Space 有两个,存放每次垃圾回收后存活的对象,即图的 S0 和 S1。
  Old Generation  Old区,也称 老年代,主要存放应用程序中生命周期长的存活对象
  JVM初始分配的内存由-Xms指定,JVM最大分配的内存由-Xmx指定。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。
  -XX:NewRatio=参数 ,可以设置Young与Old的大小比例,-server 时默认为1:2,如果太小,会使大对象直接分配到old区去,增大 full gc 的执行的次数,影响性能。
  -XX:SurvivorRatio=参数,可以设置Eden与Survivor的比例,默认为1:8,Survivio大了会浪费,如果小了的话,会使一些大对象在做minor gc时,直接从eden区潜逃到old区,让old区的gc频繁。这个参数保持默认就好了,一般情况下,对性能影响不大。
  启动后可通过 jmap –heap [pid] 查看对应不同区域所占内存大小与比例。
  由于堆的整体大小是固定的,新生代 越大,老年代 越小,越会增加 full gc 的执行的次数。所以最佳的选择是由对象的生命周期分布所决定。

JVM内存分配策略

1. 对象优先在Eden分配

    如果Eden区不足分配对象,会做一个minor gc,回收内存,尝试分配对象,如果依然不足分配,才分配到Old区。

2.大对象直接进入老年代

  大对象是指需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串及数组,虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代中分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效,

3.长期存活的对象将进入老年代

  在经历了多次的Minor GC后仍然存活:在触发了Minor GC后,存活对象被存入Survivor区在经历了多次Minor GC之后,如果仍然存活的话,则该对象被晋升到Old区。
  虚拟机既然采用了分代收集的思想来管理内存,那内存回收时就必须能识别哪些对象应当放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

4.动态对象年龄判定

    为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

5. Minor GC后Survivor空间不足就直接放入Old区

6.空间分配担保

    在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,那只会进行Minor GC;如果不允许,则也要改为进行一次Full GC。大部分情况下都还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁

如何监视GC

1.概览监视gc

jmap -heap [pid]             #查看内存分布
jstat -gcutil [pid] 1000     #每隔1s输出java进程的gc情况

2.详细监视gc

    在jvm启动参数,加入:-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log。

   gc 日志输出示例:
[GC [ParNew: 11450951K->1014116K(11673600K), 0.8698830 secs] 27569972K->17943420K(37614976K), 0.8699520 secs] [Times: user=11.28 sys=0.82, real=0.86 secs]
   表示发生一次minor GC,ParNew是新生代的gc算法,11450951K表示eden区的存活对象的内存总和,1014116K表示回收后的存活对象的内存总和,11673600K是整个eden区的内存总和。0.8699520 secs 表示minor gc花费的时间。
   27569972K 表示整个 heap区 的存活对象总和,17943420K 表示回收后整个heap区的存活对象总和,37614976K 表示整个heap区的内存总和。
[Full GC [Tenured: 27569972K->16569972K(27569972K), 180.2368177 secs] 36614976K->27569972K(37614976K), [Perm : 28671K->28635K(28672K)], 0.2371537 secs]
  表示发生了一次Full GC,整个JVM都停顿了180多秒,输出说明同上。只是Tenured: 27569972K->16569972K(27569972K) 表示的是old区,而上面是eden区。
posted @ 2020-04-23 00:52  星火燎原智勇  阅读(661)  评论(0编辑  收藏  举报