Java 对象深入探究
这篇博客是为了深入探究 Java 中对象的知识。
对象的创建
首先我们先看下一个简单创建对象的代码,看一个对象到底是如何在内存中创建的。
1 | public static void main(String[] args) { |
对应的 JVM 指令:1
2
3
4
50 new #2 <learnJava/AQS/Person>
3 dup
4 invokespecial #3 <learnJava/AQS/Person.<init>>
7 astore_1
8 return
对应指令含义:
new
: 创建一个实例对象。
dup
: 复制栈顶数值并将复制值压入栈顶
invokespecial
:调用超类构建方法, 实例初始化方法, 私有方法
astore_1
:将栈顶引用类型数值存入指定本地变量
具体的初始化过程如下:
DCL 和 volatitle 的问题
在常用的单例模式中,有一个 double check 模式 ,具体代码如下:
1 | private volatile static Singleton05 singleton05; |
在诸多的单例模式中, double check lock 是性能和简单的方式之一,在上面对象的定义中,使用了 volatitle 的关键字描述,如果此时我们没有使用 volatitlte 关键字会怎样?
因为在初始化的时候,存在一个半初始化的状态,其实是已经创建的对象,但是对象中的字段为 0,此时 DCL 检查不为空的时候,则满足了条件,即会直接返回,导致意想不到的结果。
Volatitle 关键字有两个作用
- 对象的内存区域加一个内存屏障,防止指令重排序。
- 锁定 CPU 和内存空间总线,让每一个线程的数据保持最新。
对象在内存中的存储布局
Java 对象一般分为 3 块空间:对象头
,实例数据
和对齐空间
。在数组对象中又单独增加的数据长度的空间,具体几个对象布局如下:
问题: 一个 Object 占用几个字节?
答案:16 字节,算法分为两种:
- 不开启指针压缩: 8+8+0+0 = 16
- 开启类指针压缩: 8+4+0+4 = 16
如果是 Object obj=new Object(); 则 obj 占用了 4 个字节,所以一共占用了 20 字节。
压缩对类指针和 压缩对象指针
在对象中一般会保留类的引用,称为类指针(Class Pointer)。同样,指向实例也会有一个指针,称为对象指针(OOP)。这个指针一般是 8 个字节,压缩后变成 4 个字节。
为什么要存在指针压缩呢?目的肯定是为了节省内存,为什么压缩指针能行的通呢?我个人觉得有两个原因:
对象都是 8 字节对齐的,所以指针后都是相对的 “整数“(能整除 8)
1
2
3
4
5
6
7
8
9例如: (不是 JVM 的指针压缩,只是表达指针压缩的含义)
16 00000000 00001000
32 00000000 00010000
64 00000000 00100000
可以转为:
//舍弃 3 个 0
16 00000000 00001 000
32 00000000 00010 000
64 00000000 00100 000
- 类指针和对象指针的寻址范围一般不会超过 4 字节,2^32*8 =32G(一旦超过这个值,则压缩失效)
在 JVM 中有两个参数来控制指针
UseCompressClassPointers : 类指针压缩
UseCompressOop : 对象指针压缩
对象的分配规则
- 允许在栈上直接分配开关打开,优先在栈上分配,对象如果想在栈上分配需要有两个条件:
- 标量替换:用成员变量代替对象。
- 不产生逃逸:不会被其他方法引用
- 在栈上分配空间效率比较高,直接 pop ,没有 GC 的过程。
- 如果栈空间不够用了,如果对象过大,直接进入老年区。
- 如果不大,会进入 edian 区,符合线程 ==本地分配== (进入线程独有的 buffer TLAB,不存在锁竞争,效率比较高),进入线程本地分配,不满足进入 edian 区。