Java 新建对象及对象存储

    科技2024-03-25  88

    关于JAVA new Object() 新建对象的几个问题

    问题1:对象的创建过程

    第一行汇编码,申请空间,并初始化成员变量为默认值(半初始化) 第二行:dup 复制 (因为第三行的invokespecial会消耗一个引用,所以必须先复制一个) 第三行:调用T的 构造方法,初始化m为8 第四行:astore-1 把t和new的对象连接起来

    第二题: 单例模式:应用运行期间内存中只能有一个对象

    第一种写法:一来先new一个private static 的对象,将构造方法设为 private ,设置一个public函数getInstance来获取这个对象 第二种写法: 要在需要的时候才new这个对象,先不new,等到需要的时候new。 getInstance的时候判断对象是否为空,不是空就直接get. 但是这种方法是线程不安全(多线程同时访问时可能会有多个new)

    解决方法:getInstance上锁(synchronized):整个方法加锁,锁粒度太粗。因为整个方法都拒绝多线程访问,若方法中含有较多业务代码,效率会很低。 解决方法,只对对象为空时的代码同步: 但是在一个线程判断对象为空后,获得锁之前,可能已经有另外一个线程把对象给new出来了。所以有最后的写法(DCL, double check lock): 判断两次INSTANCE是否为空(上锁前后)

    回到问题:DCL中需不需要加volatile?

    volatile 的两个功能:

    线程可见性禁止指令重排序

    回顾一下新建对象时发生的:调用构造器初始化和建立链接的两条指令如果发生重排序会怎么样?

    线程1在链接时会连到一个半初始化的对象上(t不为空了), 线程二来判断是否为空(不为空,已经半初始化了),那么就直接get了这个半初始化的对象m=0 所以要加 volatile !

    第三题:对象在内存中的存储布局

    对齐:前面三个部分加起来的bit数不能被8整除,就补齐 用ClassLayout里的parseInstance函数将对象存储布局打印 执行结果(这里没有实例数据 0Bytes):

    一共16个字节

    第四题: 对象头具体包括什么(mark word classpointer 主要是锁的信息)

    锁升级的过程:synchronized 是对对象上锁而不是对代码上锁 打印输出上锁前后的对象存储布局: 可以看出markword里存储的就是锁信息: 对于一个刚刚new出来的对象,先上的是偏向锁,再是自旋锁(无锁,lock-free,轻量级锁),再是重量级锁

    偏向锁(biased lock):第一个线程来时,只需要贴上名片,因为synchronized是可重入的,又没有锁竞争,效率很高 当第二个线程来要与第一个线程竞争锁,先把偏向锁撤销,然后两个线程用CAS(compare and swap)的方式竞争锁

    CAS的过程:每个线程要对这个对象做操作时先读出来,再计算,如果写回去的时候检查它还是原来读出来的数,那说明这中间没线程动过它,就成功了,如果写回去的时候发现它已经不是原来那个值了,那就重新读出来,再计算,再写回(比较)

    这样就不用加锁了 但是会有ABA问题,可以用加版本的方式解决ABA问题

    第五题:对象怎么定位

    句柄方式唯一的优势:对象小,垃圾回收时不用频繁改动t 缺点:两次访问

    第6题:对象怎么分配:

    优先在栈中分配(声明周期短,不需要GC,效率大概比在堆中分配快一倍) 可以进行栈上分配的条件:1.逃逸分析 DoEscapeAnalysis(没有别的方法用这个对象)2.可以进行标量替换EliminateAllocation(这个对象可以用在栈中存的成员变量代替)

    不能放入栈中的先判断大不大,大的话直接放入old区老年代 不大的话,判断是否符合线程本地分配(Thread Local Allocat Buffer), 如果符合,那么放入线程自己独有的一小块内存中,不需要锁。如果不符合,那么需要加锁竞争内存,但都是分配进堆中的EDEN区

    分代年龄,存在markword中

    第7题:一个Object 占多少字节

    Object o = new Object; o, 普通对象指针 Ordinary object pointers (OOPS) 占4个字节 不考虑o,一个Object也不一定是16个字节 查看Java的命令参数 因为在64位的机器上,类指针应该是8字节,但是由于默认开启UseCompressedClassPointers 这个指针被压缩到4字节,同时实例数据里如果有普通对象指针,比如成员变量有一个字符串或者数组,这个指针原本也是8字节,但是默认UseCompressedOops,就压缩到4字节 -XX:-UseCompressedClassPointers, 去掉压缩类指针

    那么什么情况下不压缩? 答:4个字节的指针,能寻址的最大空间是多大:32G 如果堆内存空间超过32G,压缩自动不起作用?(这里没讲清楚)

    (文中截图来源马士兵老师公开课)

    Processed: 0.011, SQL: 8