对象是 Java 程序运行的核心,而 JVM 的对象管理机制直接影响程序的运行效率和内存管理能力。
在 Java 中,对象的创建过程离不开类的加载与初始化,因此理解类加载的原理和对象的内存布局,是掌握 JVM 性能优化的关键。
本章基于类加载机制的深入解析,将详细讲解对象的创建、内存布局、访问方式及分配策略,帮助你从理论到实践全面掌握 JVM 对象管理的底层逻辑。
丢掉你收藏的那些所谓的「面试宝典」,因为它们大多数深度不够,甚至内容还有错误,这也是为何每次面试你都回答不好的原因,你只会看完就忘,还浪费时间。
类加载是 Java 对象创建的基础。
JVM 通过类加载器将 .class
文件中的二进制数据加载到内存,并将其转化为 JVM 可以识别的运行时数据结构。以下是类加载的核心步骤:
根据《Java 虚拟机规范》,类加载分为七个阶段:
.class
文件的二进制数据加载到内存,生成 Class
对象。.class
文件的格式和内容是否符合规范,确保安全性。根据 《Java 虚拟机规范》 中的规定,类加载可以分为七个阶段,分别为 加载 (Loading)
、验证 (Verification)
、准备 (Preparation)
、解析 (Resolution)
、初始化 (Initialization)
、使用 (Using)
和 卸载 (Unloading)
,其中 验证
、准备
和 解析
三个阶段整体又称为 链接 (Linking)
。
类加载就像从蓝图设计到建筑施工的过程:
加载阶段主要是使用 "类加载器" 将本地或者远程网络中的字节码文件,通过读字节流的方式加载到 Java 虚拟机内存中。在加载阶段中 Java 虚拟机主要完成以下三件事情:
java.lang.Class
对象,作为方法区中这个类的各种数据的访问入口。其中常用的类加载器有三种,分别是:
类加载器 | 描述 |
---|---|
引导类加载器 BootstrapClassLoader | 引导类加载器是使用 C++ 语言实现的,用于加载 Java 中的核心类库的,一般会加载 JAVA_HOME 目录下的 /jre/lib 文件夹下的 jar 和配置。 |
扩展类加载器 ExtClassLoader | 扩展类加载器主要负责加载 Java 的扩展类库,一般会加载 JAVA_HOME 目录下的 /jre/lib/ext 文件夹下的 jar。 |
应用类加载器 AppClassLoader | 应用类加载器是应用程序中默认的类加载器,可以加载 CLASSPATH 变量指定目录下的 jar,并且一般情况下,我们编写的 Java 应用的类,都是使用该类加载器完成加载的。 |
当类加载完成后,JVM 开始为新对象分配内存并完成初始化。
确定分配区域
分配方式
<init>
,完成实例变量初始化。Java 对象在内存中的布局分为三部分:对象头、实例数据 和 对齐填充。
对象头包含以下内容:
对象头结构示意图
JVM 提供了两种对象访问模式:句柄池 和 直接指针。
句柄池
句柄:如果使用句柄访问对象,JAVA 堆中将会划分一块内存作为句柄池,reference 中存储的就是对象的句柄地址,句柄中包含对象实例数据与类型数据。
优点:对象内存地址变化时,只需更新句柄,而无需修改引用。
直接指针
如果使用直接指针访问,则 reference 存储对象地址。优点:访问速度快,少了一次间接访问。
JVM 的内存分配策略与垃圾回收机制密切相关。以下是常见的内存分配方式:
对象主要分配在新生代的 Eden 区上,如果启动了本地线程分配缓冲,将按线程优先在 TLAB 上分配。少数情况下也可能直接分配在老年代中,分配的规则并不是百分之百固定的。
大对象直接进入老年代
虚拟机提供了一个 -XX:PretenureSizeThreshold
参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在 Eden 区和及两个 Survivor 区之间发生大量的内存复制。
长期存活的对象将进入老年代
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并且对象年龄设为 1。
对象在 Survivor 空间中每“熬过”一次 Minor GC,年龄就增加 1 岁,当它的年龄到达一定程度(最大为 15 岁),就将会被晋升到老年代。
对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold
设置。
对象是否能够晋升到老年代,也不全由-XX:MaxTenuringThreshold
参数控制,如果 Survivor 空间中相同年龄的所有对象大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
空间分配担保
新生代在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象之和(或者历次晋升老年代对象的平均大小)。
如果这个条件不成立,那么虚拟机将直接进行 Full GC 动作;如果这个条件成立,那么虚拟机就会进行一次 Minor GC 操作,但是这次 Minor GC 是有风险的,因为比较的值是平均值,可能出现极端的情况 —— 大量对象在 Minor GC 后还存活,这时就只好在失败后重新发起一次 Full GC。
本章深入解析了类加载机制对对象创建的支持,探讨了 JVM 的内存布局、访问方式及分配策略。
通过理解这些底层原理,开发者可以有效优化代码性能,并在内存问题排查中更加游刃有余。
下一章,我们将聚焦垃圾回收机制,解读 JVM 的内存回收策略与实现细节。