对象创建过程与内存分配

  • Post author:
  • Post category:其他




对象创建过程与内存分配



对象创建主要流程

  1. 类加载检查

    例如:虚拟机接到一个new User() 指令的时候,会先去检查User()这个符号引用是否在常量池中,并且检查User类是否已经被加载,如果没有,就必须先加载类。

  2. 分配内存

    通过类加载检查之后,就会为new 出来的对象分配内存。

    划分内存的方法:

    ​ ①指针碰撞

    ​ ②空闲列表

    并发问题解决办法:

    ​ ①CAS

    ​ ②本地线程分配缓冲(Thread Local Allocation Buffer )

  3. 初始化

    内存分配完成后,将分配到的内存空间除对象头以外都初始化为零值,如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

  4. 设置对象头

    对象在内存中分为三块区域:

    ①对象头:hashcode,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID ,偏向时间戳,类型指针(类元数据的指针),数组对象有数组长度。

    ②实例数据:对象真正存储的有效信息,也是程序中所定义的各种类型的字段内容。

    ③对齐填充:保持对象大小是8的倍数,读取是最快的,这就是对齐的原因

  5. 执行方法

    属性赋值,和执行构造方法。



堆内存分配方式

分配方式有

“指针碰撞” 和 “空闲列表”

两种,选择那种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。GC器的算法是“标记清除”(不规整、有内存碎片)还是“”标记整理(规整),复制算法也是规整的。



指针碰撞

用过的内存全部整合到一边,没有用过的内存放在另一边,中间有个分界指针,只需要向着没用过的内存方向将该指针移动内存大小位置即可。在堆内存规整的情况下使用(Serial,ParNew)



空闲列表

虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块足够大的内存块来划分给对象实例,然后更新表记录。堆内存不规整的情况下使用(CMS)。



线程安全保证方式



CAS+失败重试

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



TLAB

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



栈上分配

如果确定一个对象的作用域不会逃逸出方法之外,那可以将这个对象分配在栈上,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,无须通过垃圾收集器回收,可以减小垃圾收集器的负载。

JVM允许将线程私有的对象标量替换后分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统性能。



逃逸分析

逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。



标量替换

允许将对象打散分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。

-XX:DoEscapeAnalysis启用逃逸分析;

-XX:+EliminateAllocations开启标量替换(默认打开);

-XX:+PrintEscapeAnalysis查看逃逸分析的筛选结果



版权声明:本文为qq_29991091原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。