JVM对象创建和内存分配机制


JVM对象创建

JVM对象创建的主流程

  1. 类加载检查:
    • 虚拟机在接收到一条new 指令时,会先检查对象是否被加载到内存中,如果没有进入类加载流程
  2. 分配内存:
    • 在类加载完成时,类对象所需的内存大小就已经确认下来,所以虚拟机只需要在java堆中划分一块固定大小的区域来保存对象。
    • 内存分配方式:
      • 指针碰撞:在内存空间中维护一个指针,每次指针向后移动一段与对象大小一致的距离。这种方法保证内存空间时规整的,已经使用的在指针前面,未使用的在指针后面。
      • 空闲列表:如果内存空间是散乱的,已使用和未使用的区域相互交错,虚拟机就会维护一个空闲列表来记录哪些区域是可用的。在保存对象时,会查找一块足够大的内存空间来保存对象,并更新空闲列表。
    • 高并发下内存争抢问题:
      • CAS:采用CAS算法,让线程A尝试获取保存对象,如果保存不成功,则重试,直到成功。
      • 本地线程分配缓冲(Thread Local Allocation Buffer,TLAB):java线程开启时,在内存空闲划分一块区域来保存该线程产生的对象。可以通过 -XX:+UseTLAB(默认开启),通过 -XX:TLABSize=4k 设置分配区域大小。
  3. 初始化:
    • 将分配到的内存都设置为0值(二级制码都是0/1),这保证了java代码在不赋予初值的情况下也能被使用。
  4. 设置对象头
    • 初始化零值之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。
  5. 执行init方法:
    • 给对象的属性赋值。

对象内存分配

对象内存分配流程

对象栈内分配

我们知道对象是在堆内进行分配内存的,当对象没有被引用时,我们需要依靠 GC 进行回收,当临时对象过多时,会对 GC 造成很大的压力,从而影响应用的性能(GC 会导致STOP THE WORLD)。为了避免这种情况,JVM通过 逃逸分析 来判断对象是否在方法外被引用。
如果对象没有逃逸且栈帧空间足够的情况下,JVM会将对象保存在栈帧中,这样在方法结束时,该对象会随着栈帧的出栈而被销毁,进而减轻 GC 的压力。
通过开启逃逸分析参数(-XX:+DoEscapeAnalysis),JDK7之后默认开启逃逸分析

// 对象没有逃逸
public void doSomething(){
    Something st = new Something();
    st.setId();
    st.setThing();
    // 保存数据操作
}
// 对象逃逸
public Something getSomething(){
    Something st = new Something();
    st.setId();
    st.setThing();
    return st;
}

标量替换

在通过逃逸分析判断对象没有被外部引用时,会将对象进行分解,不在栈帧中创建,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替。
在JVM将对象保存到栈帧中时,可能出现栈帧中存在足够的空间但是空间不连续的情况,这个时候需要我们开启JVM的标量替换功能。
开启标量替换参数(-XX:+EliminateAllocations),JDK7之后默认开启

大对象

JVM在判断大对象时,会将大对象直接放入老年代。
如何判断大对象?
JVM参数 -XX:PretenureSizeThreshold 可以设置大对象的阈值。
为什么设置大对象?
避免在minor GC 过程中,大对象的复制操作带来的性能问题。

对象动态年龄判断

如果对象年龄n1+n2+…+nx的大小超过S0/S1的**50%**,那么年龄 x 及以上的对象会被直接移入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。
可以通过-XX:TargetSurvivorRatio 指定大小。

老年代空间分配担保机制

年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间。
如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象),就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了。
如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。
如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生”OOM”
当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”


文章作者: zhouxh-z
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zhouxh-z !
  目录