对象创建过程

创建对象的几种方式

  1. new关键字  Object obj = new Object();

  2. 反射

  3.克隆

  4.反序列化

创建对象的过程

1.创建过程

(1)检查是否加载过   

  虚拟机解析new指令,首先检查常量池是否有类的符号引用,并且检查是否已经加载解析和初始化,没有就执行类加载过程

(2)分配内存  
  虚拟机在java堆中分配空间。如果内存是规整的,虚拟机通过“指针碰撞”分配内存、如果内存不规整的,虚拟机维护一个内存块使用情况表,通过“空闲列表”分配内存
  指针碰撞:堆分为未分配和已分配两个区域,使用指针进行划分,适用于没用内存碎片的情况下(Serial、ParNew等GC收集器)
  空闲列表:维护一个列表,记录可用的内存块信息,当分配操作发生时,从列表中找到一个足够大的内存块分配给对象实例,并更新列表上的记录(CMS收集器)
(3)初始化  
  内存空间初始化为零值。这也是有些对象不赋值就可以使用的原因。
(4)设置对象头  
  在对象的对象头中设置:对象是哪个类的实例,如何找到类的元数据信息、对象的哈希码、对象的Gc分代年龄等信息。
(5)执行init指令 
  以上4步一个新的对象已经产生,但是所有的字段都是零。接着字节码解析器调用init指令,这个对象才算完全产生出来。

2.多线程下分配空间时安全性问题

  TLAB: 为每一个线程预先分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用CAS进行内存分配。 

  CAS: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。

  

3.对象的内存布局

(1)对象头
  a.运行时数据包括哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID和偏向时间戳等,这部分数据在32位和64位虚拟机中的长度分别为32bit和64bit,官方称为"Mark Word"。Mark Word被设计成非固定的数据结构,以实现在有限空间内保存尽可能多的数据。32位的虚拟机中,对象未被锁定的状态下,Mark Word的32bit中25bit存储对象的HashCode、4bit存储对象分代年龄、2bit存储锁标志位、1bit固定为0,具体如下:其它状态(轻量级锁定、重量级锁定、GC锁定、可偏向锁)下Mark Word的存储内容如下:
  
  b.对象头的类型指针指向该对象的类元数据,虚拟机通过这个指针可以确定该对象是哪个类的实例。
(2)实例数据实例数据就是在程序代码中所定义的各种类型的字段,包括从父类继承的,这部分的存储顺序会受到虚拟机分配策略和字段在源码中定义顺序的影响。
(3)对齐填充  //由于HotSpot的自动内存管理要求对象的起始地址必须是8字节的整数倍,即对象的大小必须是8字节的整数倍,对象头的数据正好是8的整数倍,所以当实例数据不够8字节整数倍时,需要通过对齐填充进行补全。

对象都是在堆中么?(逃逸)

  逃逸的基本行为是分析对象的动态作用域,分析指针动态范围的方法称之为逃逸分析  当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他过程或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。

1.方法逃逸

    //方法逃逸
    public Dog testEscape(){
        Dog dog = new Dog();
        return dog;
    }
    //未发生方法逃逸
    public void testEscape(){
        Dog dog = new Dog();
        dog.run();
    }

2.线程逃逸

复制代码
    //线程逃逸
    public static void testEscape(){
        Dog dog = new Dog("小黑");
        new Thread(new Runnable() {
            @Override
            public void run() {
                dog.run();
            }
        }).start();
    }
复制代码

3.栈上分配  

  在一般应用中,不会逃逸的局部对象占比很大,如果使用栈上分配,那大量对象会随着方法结束而自动销毁,垃圾回收系统压力就小很多。

4.同步消除

  线程同步本身比较耗时,如果确定一个变量不会逃逸出线程,无法被其它线程访问到,那这个变量的读写就不会存在竞争,对这个变量的同步措施可以清除。

5.标量替换。

 (1)标量就是不可分割的量,java中基本数据类型,reference类型都是标量。相对的一个数据可以继续分解,它就是聚合量(aggregate)。
 (2)如果把一个对象拆散,将其成员变量恢复到基本类型来访问就叫做标量替换。
 (3) 如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那么程序真正执行的时候将可能不创建这个对象,而改为直接在>栈上创建若干个成员变量。

6.相关JVM参数

  -XX:+DoEscapeAnalysis 开启逃逸分析
  -XX:+PrintEscapeAnalysis 开启逃逸分析后,可通过此参数查看分析结果。
  -XX:+EliminateAllocations 开启标量替换
  -XX:+EliminateLocks 开启同步消除
  -XX:+PrintEliminateAllocations 开启标量替换后,查看标量替换情况

7.总结

 如果项目中的大部分方法中的对象都不会逃逸,那么可以开启逃逸分析、开启标量替换、开启同步擦除。 

    

posted @   -Lucas  阅读(130)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示