上篇文章说了jvisualvm工具查看年轻代老年代gc过程。
Jvisualvm&内存模型剖析-JVM(五)https://blog.csdn.net/ke1ying/article/details/131524708
- jvm创建对象
之前我们介绍了类的加载,这篇文章要介绍类的创建,过程主要是:
1、类的加载检查-----(是否已加载)
2、否的话,则加载类,是的话直接进入第三步
3、直接分配内存空间。
4、初始化
5、设置对象头
6、执行<init>方法
- 类加载检查
虚拟机遇到new指令,首先检测这个类是否在常量池中定位到类的符号引用,检测这个符号是否被加载、解析和初始化过,没有的话则必须先加载类。
- 分配内存
接下来虚拟机为新生对象分配内存,为对象分配内存等同于把一定大小的内存从java堆中划分出来。
但这会有两个问题:
- 如何划分内存。
- 并发情况下,在给对象A划分内存时候,指针还没来得及修改,在同一位置又给B划分内存。
划分内存分为两种,指针碰撞和空闲列表。
指针碰撞(Bump the Pointer):默认是指针碰撞,如果java内存是绝对规整的,所有用过内存都放在一边,空闲的放在另一边,中间放指针分解指示器,当分配内存后,就把指针移动到右侧。
空闲列表(Free List):如果java堆内存并不是规整的,已使用和空闲的内存相互交错,那么就没办法简单的指针碰撞,虚拟机会维护一个空闲列表,看哪些可用。
第一个划分问题策略已给出,那么如何解决并发问题呢。
有两种方式可以解决,CAS和本地线程分配缓冲TLAB。
CAS(compare and swap):比较和交换用cas配合失败重试机制来解决并发问题。
TLAB(thread local allocation buffer):把内存划分的动作按照线程划分在不同的空间,即每个线程在java堆中预分配一小块内存。通过-XX:+/UseTLAB参数设置虚拟机是否使用TLAB,而它的大小设置用的是-XX:TLABSize。
3、初始化
初始化就是前面说过的,当initDate在创建阶段,并不是直接把666赋值,而是先赋值0,String会先赋值null等。
- 设置对象头
前面初始化为0之后,虚拟机要对对象进行必要设置,例如哪个对象是哪个类实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息都存在对象的对象头Object Header中。
在hotSpot虚拟机中,对象内存中布局分为三块区域:对象头(header)、实例数据(instance data)、和对齐填充(Padding)。
- 对象头(Object header)
Mark word:
有Mark word标记字段(32位占4字节,64位占8字节),自行运行时数据:哈希值,GC分代年龄,锁状态标志,线程持有锁,偏向线程ID,偏向时间戳。
从图可以看到分代年龄是4bit,最大是15,所以他的分代年龄不能超过15。
klass Pointer:
有klass Pointer类型指针(开启压缩占4字节,关闭压缩占8字节),类的元数据指针。
前面说了:一个对象new出来放在堆里,这时候对象头有一个klass Pointer指向方法区(元空间)的类的元素信息。(这里不是class,kclass是c++底层实现的)
数组:
数组长度(4个字节,只有数组才有)
打印对象信息我们需要引入这个maven包,如图所示。
Object大概16bytes,offset是偏移量。
前面两行数据存储的就是mark word。
第三行就是klass pointer,size显示8bytes
第四行是object alignment,对齐则就是填充padding,保证对象是8bytes的整数倍。
前面三行原本是12个字节,但因为对齐,所以多了4个字节,保证object是8的整数倍,这样计算可以保证计算机效率最高。
数组前面两行也是mark word。
第三行还是klass pointer。
第四行则是我们数组的长度,显示4个字节。
于是对齐就是0,因为已经是16个字节,不需要凑整为8的整数倍。
对象前面两行也是mark word。
第三行还是klass pointer。
第四行int类型默认4个字节
Byte类型b则是一个字节
字节会先内部对齐
之后则是name和object的对象默认都是4个字节
最后再次对齐成为8的倍数,28+4
指针压缩是什么?
前面说了klass没开启压缩是8个字节,开启是4个字节,那么我们刚打印的都是4个字节,为什么呢,因为jdk1.6之后默认都是指针压缩后的。
--XX:+UseCompressedOops(默认开启的)
--XX:-UseCompressedOops关闭命令
- 执行<init>方法
前面说了初始化会为0和null,这时候才会赋值代码里真实的值。