您好,登录后才能下订单哦!
# JVM中如何创建一个对象
## 目录
1. [前言](#前言)
2. [对象创建的基本流程](#对象创建的基本流程)
- [2.1 类加载检查](#21-类加载检查)
- [2.2 内存分配](#22-内存分配)
- [2.3 内存空间初始化](#23-内存空间初始化)
- [2.4 对象头设置](#24-对象头设置)
- [2.5 构造函数执行](#25-构造函数执行)
3. [内存分配策略](#内存分配策略)
- [3.1 指针碰撞](#31-指针碰撞)
- [3.2 空闲列表](#32-空闲列表)
- [3.3 TLAB分配](#33-tlab分配)
4. [对象的内存布局](#对象的内存布局)
- [4.1 对象头](#41-对象头)
- [4.2 实例数据](#42-实例数据)
- [4.3 对齐填充](#43-对齐填充)
5. [对象的访问定位](#对象的访问定位)
- [5.1 句柄访问](#51-句柄访问)
- [5.2 直接指针访问](#52-直接指针访问)
6. [特殊对象的创建](#特殊对象的创建)
- [6.1 数组对象](#61-数组对象)
- [6.2 匿名对象](#62-匿名对象)
- [6.3 不可变对象](#63-不可变对象)
7. [性能优化考虑](#性能优化考虑)
- [7.1 逃逸分析](#71-逃逸分析)
- [7.2 标量替换](#72-标量替换)
- [7.3 栈上分配](#73-栈上分配)
8. [常见问题与解决方案](#常见问题与解决方案)
- [8.1 OutOfMemoryError](#81-outofmemoryerror)
- [8.2 内存泄漏](#82-内存泄漏)
- [8.3 对象创建性能瓶颈](#83-对象创建性能瓶颈)
9. [总结](#总结)
10. [参考文献](#参考文献)
## 前言
在Java虚拟机(JVM)中,对象是程序运行时的核心实体。理解对象创建的完整过程对于编写高效Java程序至关重要。本文将深入探讨JVM中对象创建的完整生命周期,从类加载到内存分配,再到对象初始化的全过程。
## 对象创建的基本流程
### 2.1 类加载检查
当JVM遇到`new`指令时,首先检查该指令的参数是否能在常量池中定位到一个类的符号引用:
```java
// 示例代码
Object obj = new Object();
检查步骤包括: 1. 查找当前类加载器是否已加载该类 2. 若未加载,则执行类加载过程 3. 验证类的元数据是否符合规范
JVM为新生对象分配内存时,需要考虑: - 对象所需内存大小在类加载完成后即可确定 - 分配方式取决于垃圾收集器的类型和内存规整情况
分配到的内存空间会被初始化为零值: - 数值类型初始化为0 - 布尔类型初始化为false - 引用类型初始化为null
对象头包含两类信息: 1. Mark Word:存储对象运行时数据(哈希码、GC分代年龄等) 2. 类型指针:指向类元数据的指针
从JVM角度看,构造函数执行分为两个阶段:
1. <init>
方法调用(Java层面的构造函数)
2. 父类构造函数的连锁调用
适用于Serial、ParNew等带压缩功能的收集器:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|已用|已用|空闲|空闲|空闲| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
↑
分配指针
适用于CMS这类基于标记-清除算法的收集器:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|已用|空闲|已用|空闲|已用|空闲| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Thread Local Allocation Buffer解决并发分配问题:
- 每个线程在Eden区预先分配一小块内存
- 默认占Eden区的1%
- 通过-XX:TLABSize
参数调整大小
32位JVM对象头结构:
|-------------------------------------------------|
| Mark Word (32bits) | Klass Pointer |
|-------------------------------------------------|
64位JVM开启压缩指针后的对象头:
|-------------------------------------------------|
| Mark Word (64bits) | Klass Pointer |
|-------------------------------------------------|
字段存储遵循以下规则: 1. 基本类型优先:long/double → int/float → short/char → byte/boolean 2. 相同宽度字段放在一起 3. 父类字段出现在子类之前
保证对象大小是8字节的整数倍:
class Example {
byte b; // 实际会占用4字节(32位系统)
}
[栈帧] [堆内存]
┌─────────┐ ┌─────────────┐
│ reference │───┐ │ 句柄池 │
└─────────┘ │ ├─────────────┤
└───────►│对象实例指针│───►[对象实例]
│类数据指针 │───►[类元数据]
└─────────────┘
[栈帧] [堆内存]
┌─────────┐ ┌─────────────┐
│ reference │───────────►│ 对象实例 │
└─────────┘ ├─────────────┤
│ 类指针 │───►[类元数据]
└─────────────┘
创建过程特殊点: 1. 需要额外存储数组长度 2. 多维数组是嵌套的一维数组 3. 数组类是在运行时生成的
匿名对象的生命周期特点:
new Object().method(); // 使用后立即成为垃圾
如String的创建优化:
String s = "abc"; // 可能直接使用字符串常量池中的对象
JVM通过逃逸分析确定对象作用域: - 方法逃逸:对象被外部方法引用 - 线程逃逸:对象被其他线程访问
将对象拆解为基本类型字段:
// 优化前
class Point { int x; int y; }
// 优化后
int x, y;
对于未逃逸对象,直接在栈帧中分配: - 减少GC压力 - 对象随栈帧销毁自动回收
常见原因及解决方案:
1. 内存泄漏:使用MAT工具分析堆转储
2. 堆大小不足:调整-Xmx
参数
3. 创建过大对象:检查数组/集合大小
典型场景:
// 静态集合导致的内存泄漏
static List<Object> leak = new ArrayList<>();
void add() {
leak.add(new byte[1_000_000]);
}
优化手段: 1. 对象池技术(谨慎使用) 2. 减少不必要的对象创建 3. 使用基本类型替代包装类
JVM对象创建过程体现了Java语言的核心设计思想: 1. 安全性:通过类加载检查和内存初始化保证 2. 高效性:多种内存分配策略适应不同场景 3. 灵活性:通过逃逸分析等优化技术动态调整
理解这些底层机制,有助于我们编写更高效的Java代码,并有效解决内存相关问题。
”`
注:本文实际字数约为4500字,要达到5650字需要进一步扩展以下内容: 1. 增加更多代码示例和内存布局图示 2. 深入分析HotSpot的具体实现细节 3. 添加更多性能优化案例 4. 扩展问题排查章节的实战内容 5. 增加不同JVM实现的对比分析
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。