本文最后更新于:星期四, 六月 18日 2020, 9:01 上午

对象的创建:

  1. 检查被new的对象的类是否已经被加载,解析,初始化过。若没有,执行相应的类加载过程
  2. 分配内存(两种方式)
             1. 指针碰撞:用Serial、ParNew等带Compact过程的垃圾收集器
      2. 空闲列表:用CMS这种基于Mark-Sweep算法的垃圾收集器。
    1. 内存分配不是线程安全的:在并发情况下,可能出现在给对象A分配内存,指针还没来得及修改,对象B又使用了原来的指针来分配内存。解决方法:
      1. 对分配内存空间的动作进行同步处理:用CAS配上失败重试。
      2. 把内存分配的动作按照不同线程分配到不同空间中进行:每个线程在堆中预先分配一小块本地线程分配缓冲(TLAB),哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。
    2. 内存分配完后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这保证了比如声名int a;不用赋值也能直接使用(a=0),程序能访问到这些字段的数据类型所对应的零值。
    3. 虚拟机对对象进行必要的设置,如:对象是哪个类的实例,如何找到类的元数据信息,对象的HashCode,对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)中。
    4. 从虚拟机来看,新的对象已经产生,但是从Java程序来看,创建才刚刚开始,方法还没执行,所有字段都还为零。所以执行new指令之后会接着执行方法,把对象进行初始化,这样真正可用的对象才是完全产生出来。

对象的内存布局:

  1. 对象头:
     1. 存储对象自身的运行时数据:HashCode,GC分代年龄,锁状态标识,线程持有的锁,偏向线程ID,偏向时间戳等。
     2. 类型指针:即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
     3. 若对象是个数组,对象头中还有一块用来记录数组长度。
  2. 实例数据:
     1. 对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。无论是父类继承的,还是子类自己定义的,都需要记录下来。实例数据的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义的顺序的影响。
     2. 分配策略为:longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers)。相同宽度的字段总是被分配到一起。且父类定义的变量会出现在子类之前。
  3. 对齐填充:占位符的作用。虚拟机要求对象大小必须是8字节的整数倍,而对象头大小刚好是8字节的倍数(1倍或2倍),因此当实例数据部分没有对齐时,就需要通过这部分来补齐。

对象访问:


JVM     

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

数组实现固定长度的循环队列 上一篇
String对象的intern()方法。 下一篇