码字不易,欢迎大家转载,烦请注明出处;谢谢配合

Java体系的内存管理主要体现在两方面,一方面是给对象分配内存,另一方面则是回收分配给对象的内存;之前我们花了较大的篇幅介绍垃圾收集涉及到的算法,以及垃圾收集器,本文将同大家一起探讨内存的分配。

栈上分配

小对象会首先尝试在栈上进行分配,使用完成后,支持弹出栈;具有如下特点:

  • 线程私有小对象;

  • 无逃逸时;

  • 支持标量替换;

一般不需要进行参数调整

TLAB (Thread Local Allocation Buffer)

Eden会为线程开辟一个小的空间,用于尝试小对象的分配;线程本地分配TLAB,该空间位于Eden区,大小默认为Eden的1%;默认开启;具有如下特点:

  • 小对象

  • 占用Eden,默认1%

  • 多线程分配的时候,不用争抢Eden,提高效率

一般也不许进行参数调整。

-XX:+UseTLAB 开启,-XX:-UseTLAB 关闭

对象优先在Eden分配

大多数情况下,对象在新生代Eden分配,当Eden没有足够的内存空间时,将发生一次Minor GC。这里可能有些人还不明白Minor GC,Major GC ,Full GC 有什么区别。

Minor GC :指新生代GC,包括Eden和Survivor
Major GC : 指老年代GC
Full GC :指整个堆空间GC

大对象直接进入老年代

所谓的大对象就是指需要连续内存空间的Java对象,例如很长的字符串或者数组;大对象对虚拟机来说是一个坏消息,经常出现大对象导致虚拟机明明还有不少的内存空间时就提前触发垃圾收集已获得连续的内存空间来安置这些大对象。

虚拟机允许通过-XX:PretenureSizeThreshold ,使大小大于设定值的对象直接进入老年代。

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

既然垃圾收集器采用分代收集的思想管理内存,那么它应该能够区别出那些对象应该放在新生代,那些对象应该放在老年代;为了实现分代,虚拟机给每个对象创建了一个年龄计数器,如果对象在Eden中创建,经过一次Minor GC 后,如果 Survivor 空间大小能够容纳该对象的话,该对象就被复制到Survivor中并将年龄设置为1,没经过一次Minor GC 对象年龄+1,直到年龄达到老年代的阈值,该对象就会被复制到老年代。

可以通过设定-XX:MaxTenuringThreshold 来设定晋升到老年代的阈值

动态对象年龄判断

深入理解JVM原理中描述

为了更好的使用不同的程序,并不是所有的对象都需要达到老年代阈值以后
才能进入;当Survivor中相同年龄对象的总和大于Survivor空间的一半,
那么大于该年龄的对象就可以直接进入老年代,
无须达到XX:MaxTenuringThreShold要求的年龄。

这里的描述与实际代码稍有出入,应当是累积年龄大于Survivor空间的一半,那么大于等于该年龄的对象就直接进入老年代。

空间分配担保

当虚拟机进行Minor GC 之前,会检查老年代连续的内存空间是否大于新生代所有对象的总空间,如果大于Minor GC确保是安全的;如果小于,并且HandlePromotionFailure设置为允许空间分配担保时,老年代会检查剩余的连续内存空间大小是否大于晋升到老年代对象的平均大小,如果大于平均大小则进行Minor GC,尽管此次Minor GC是有风险的,如果小于平均大小,或者HandlePromotionFailure设置为不允许冒险,则改为进行一次Full GC;如果Minor GC 失败,那么虚拟机会重新发起一次Full GC,但这似乎绕了一个大圈子。

通常情况下为了避免频繁的Full GC,HandlePromotionFailure 都会设置为允许担保。