JVM创建对象与内存分配机制深度剖析

发布于:2025-06-25 ⋅ 阅读:(22) ⋅ 点赞:(0)

对象创建

对象创建的主要流程

在这里插入图片描述

类加载检查

检查 new 这个指令的参数是否能在常量池中定位一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过。

分配内存

虚拟机根据对象所需内存大小,从堆中划分出一块内存给对象使用。

划分内存的方法

  • 指针碰撞 (默认)
    使用过的内存放在一边,没有使用过的内存放在另一边,中间用一个指针隔离开,当有新的对象加入,执行向空闲内存那边移动对象所需内存大小的距离。
  • 空闲列表
    使用过的内存和空闲内存交织在一起。维护了一个列表,记录空闲内存地址,新对象加入的时候,根据空闲列表选择存放地址。

解决多线程产生并发问题的办法
1、CAS
虚拟机采用 CAS 配合失败重试的办法保证更新操作的原子性。

2、本地线程分配缓冲(TLAB)
每个线程在 JAVA 堆中预先分配一小块内存。

初始化零值

虚拟机将分配到的内存空间都初始化为零值(不包括对象头)。

这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用。

设置对象头

在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为 3 块区域:对象头,实例数据和对齐填充。

对象头包括两部分信息,一部分是用于存储对象自身的运行时数据,如 HashCode,GC 分代年龄,锁状态标志等;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

32位对象头
在这里插入图片描述
通过上面图片可知使用 4bit 空间存储分代年龄,所以分带年龄最大值是 15。

64位对象头
在这里插入图片描述

Klass Pointer 类型指针
在这里插入图片描述

执行 init 方法

负责为属性赋值(代码中写的值),执行构造方法。

指针压缩

jdk1.6 update14 开始,在 64bit 操作系统中,JVM 支持指针压缩。

启用指针压缩:-XX:+UseCompressedOops(默认开启)
禁止指针压缩:-XX:-UseCompressedOops

为什么要进行指针压缩?

64 位指针占用较大带宽,同时 GC 也会承受较大压力, 指针压缩可以减少内存消耗。

注意:

堆内存小于 4G 时,不需要启用指针压缩,JVM 会直接去除高 32 位地址,即使用低虚拟地址空间。


堆内存大于 32G 时,压缩指针会失效,会强制使用 64 位指针。

对象内存分配

对象栈上分配

JVM 中的对象一般都是在堆上进行分配的,但是当对象不是很大,也可以为对象在栈上分配内存。

分配之前需要对对象进行逃逸分析,符合条件的对象才可以分配到栈上。

逃逸分析:分析对象的动态作用域,如果这个对象只在一个方法中使用,其余方法不会引用这个对象,说明这个对象可以存放到栈中。

分配到栈上的优点: 对象可以在方法结束时跟随栈内存一起被回收掉,不用等待 GC 回收。

标量替换
对象可以被分配到栈上,并可以被进一步分解时,JVM 不会创建对象,而是把对象的成员变量存入栈中,这些成员上会有一个标识表示它属于哪个类。

在这里插入图片描述

对象Eden,Survivor,老年代流转过程

Eden

大多数情况下,对象在新生代中 Eden 中分配,但是当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。

Minor GC/Young GC:指发生在新生代的垃圾收集动作,非常频繁,回收速度也比较块。
Major GC/Full GC:一般回收老年代,年轻代,方法区的垃圾,回收速度比 Minor GC 速度慢 10 倍以上。

Survivor

Eden 和 Survivor 默认空间大小比例是 8:1:1。

-XX:-UseAdaptiveSizePolicy 可以设置比例。

一般对象先被分配在 Eden,Eden 满了之后触发 Minor GC,此时把 Eden 区垃圾对象回收,剩余存活对象挪到空的 survivor。

当再次触发 Minor GC 时,会清除 Eden 和存放了对象的那个 Survivor,剩余的对象放入另一个 Survivor 中。

老年代

但是当 Survivor 空间大小不够存 Minor GC 后还存活的对象时,对象会被放入老年代

大对象直接进入老年代
为了解决大对象存入年轻代,进行复制操作会降低效率这个问题。通过设置 -XX:PretenureSizeThreshold 和 -XX:+UseSerialGC 让大对象直接进入老年代。

-XX:PretenureSizeThreshold 表示大小超过这个参数设置的值的对象就是大对象。
-XX:+UseSerialGC 表示使用 Serial 这个垃圾收集器才可以使用 PretenureSizeThreshold

长期存活的对象将进入老年代
如果对象大小没有那么大,会根据对象年龄判断是否需要进入老年代。对象每经历一次 Minor GC ,年龄加 1,当对象年龄到 15 岁,会被放入老年代。

对象动态年龄判断
Survivor区域里年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,那么就把年龄大于等于 n 的对象放入老年代。

老年代空间分配担保机制
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到