JVM-(五)运行时数据区-堆内存总结

本文最后更新于:May 13, 2023 pm

积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里,不积小流无以成江海。齐骥一跃,不能十步,驽马十驾,功不在舍。面对悬崖峭壁,一百年也看不出一条裂缝来,但用斧凿,能进一寸进一寸,能进一尺进一尺,不断积累,飞跃必来,突破随之。

目录

内存分配策略

又称为对象提升(Promotion)规则。

针对不同年龄段的对象分配原则如下:

优先分配到Eden区。

大对象直接分配到老年代。

  • 尽量避免程序中出现过多的大对象。最怕的就是:是大对象,但是这个对象是用一次就会死亡的那种。但是在老年代中GC是比较少的,所以这个大对象就有可能长期存放在老年代中。

长期存活的对象分配到老年代。

动态对象年龄判断

  • 如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,而无须等到MaxTenuringThreshold中要求的年龄。

空间分配担保(前提是老年代的空间足够装下)

  • -XX:HandlePromotionFailure

TLAB

TLAB(Thread Local Allocation Buffer)。TLAB和对象分配有关,可见博文《JAVA知识点-Java对象的分配详解 》

原因

  • 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据。由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的。为了避免多个线程操作同一地址,通常需要使用加锁等机制,但是这样会影响分配速度。这个时候就诞生了TLAB。

TLAB

  • 从内存模型而不是垃圾收集的角度,对Eden区继续进行划分,JVM为每个线程分配一个私有缓存区域,在Eden区内。

  • 多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能提升内存分配的吞吐量。所以,可以将这种内存分配方式称为快速分配策略。

可见图理解:

说明

  • 不是所有的对象实例都在TLAB中成功分配内存,但JVM是将TLAB作为内存分配的首选。
  • 可以通过选项 -XX:UseTLAB 设置是否开启TLAB空间。
  • 默认情况下,TLAB空间的内存非常小,仅占有整个Eden区的1%。当然,可以通过选项 -XX:TLABWasteTargetPercent 设置TLAB空间所占用Eden区的百分比大小。
  • 一旦对象在TLAB空间分配内存失败时,JVM就会尝试通过使用加锁机制确保数据操作的原子性,从而直接在Eden区(非TLAB区)中分配内存。

对象分配过程:TLAB

堆空间参数设置

Oracle官方参数说明

常用JVM参数

-XX:+PrintGCDetails:输出详细的GC处理日志。

-XX:+PrintFlagsInitial:查看所有的参数的默认初始值。

1
-XX:+PrintGCDetails -XX:+PrintFlagsInitial

-XX:+PrintFlagsFinal:查看所有参数的最终值(可能已经被修改过)

1
2
-XX:+PrintFlagsFinal -XX:SurvivorRatio=5 
//打印的参数结果值在等号前面有一个冒号(:),则表示重新赋值过

-Xms:初始堆空间内存大小(默认为本机物理内存的1/64)

-Xmx:最大堆空间内存大小(默认为本机物理内存的1/4)

-Xmn:设置新生代的大小。(初始值和最大值)

-XX:NewRatio:配置新生代与老年代在堆结构的占比。默认为2。

-XX:SurvivorRatio:设置新生代中Eden区和S0/S1空间的比例。默认为8。

-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄。

-XX:HandlePromotionFailure:是否设置空间分配担保。在JDK7之后此参数已经失效,不会在影响到虚拟机的空间分配担保策略。

在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。

如果大于,则此次Minor GC是安全的。

如果小于,则虚拟机会查看-XX:HandlePromotionFailure设置的值是否运行担保失败。

  • 如果-XX:HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小。如果大于:则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于,则改为进行一次Full GC。

  • 如果-XX:HandlePromotionFailure=false,则改为进行一次Full GC。

历次晋升的平均大小:指之前的每一次从新生代晋升到老年代的总大小除以总次数。即:(n1+n2+n3+….+Nk) / k = 平均大小。

📢注意: 虽然源码中还定义了-XX:HandlePromotionFailure参数,但是在代码中已经不会使用它了。JDK7之后的规则变为:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。