• 深入解析JAVA对象的创建过程
  • 发布于 2个月前
  • 503 热度
    0 评论
对象的创建
首先我们先看下一个简单创建对象的代码,看一个对象到底是如何在内存中创建的。
public static void main(String[] args) {
        Person obj = new Person();
    }
public class Person {
    private int age=8;
    private String name="fu";

}
对应的 JVM 指令:
0 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 模式 ,具体代码如下:
private volatile static Singleton05 singleton05;
// 堆代码 duidaima.com
//double check 性能最好
public static Singleton05 getInstance() {
    if (singleton05 == null) {
        synchronized(Singleton05.class) {
            if (singleton05 == null) {
                singleton05 = new Singleton05();
            }
        }
    }
    return singleton05;
}
在诸多的单例模式中, double check lock 是性能和简单的方式之一,在上面对象的定义中,使用了 volatitle 的关键字描述,如果此时我们没有使用 volatitlte 关键字会怎样?

因为在初始化的时候,存在一个半初始化的状态,其实是已经创建的对象,但是对象中的字段为 0,此时 DCL 检查不为空的时候,则满足了条件,即会直接返回,导致意想不到的结果。

Volatitle 关键字有两个作用
1.对象的内存区域加一个内存屏障,防止指令重排序。

2.锁定 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 个字节。

为什么要存在指针压缩呢?目的肯定是为了节省内存,为什么压缩指针能行的通呢?我个人觉得有两个原因:
1.对象都是 8 字节对齐的,所以指针后都是相对的 “整数“(能整除 8)

例如: (不是 JVM 的指针压缩,只是表达指针压缩的含义)
16  00000000 00001000
32  00000000 00010000
64  00000000 00100000 
可以转为:
  //舍弃 3 个 0 
16  00000000 00001 000
32  00000000 00010 000
64  00000000 00100 000

2.类指针和对象指针的寻址范围一般不会超过 4 字节,2^32*8 =32G(一旦超过这个值,则压缩失效)


在 JVM 中有两个参数来控制指针:
UseCompressClassPointers : 类指针压缩
UseCompressOop : 对象指针压缩

对象的分配规则
允许在栈上直接分配开关打开,优先在栈上分配,对象如果想在栈上分配需要有两个条件:
1.标量替换:用成员变量代替对象。

2.不产生逃逸:不会被其他方法引用


在栈上分配空间效率比较高,直接 pop ,没有 GC 的过程。
如果栈空间不够用了,如果对象过大,直接进入老年区。

如果不大,会进入 edian 区,符合线程 ==本地分配== (进入线程独有的 buffer TLAB,不存在锁竞争,效率比较高),进入线程本地分配,不满足进入 edian 区。

用户评论