1,对象的创建
1)new指令处理:
检查new指令的参数能否在常量池中定位到一个类的符号引用。执行类的加载、解析、初始化过程。
2)对象所需的内存大小:类加载完成后便可以确定。为对象分配空间等同于把一块确定大小的内存从Java堆中划分出来。
3)内存分配算法
指针碰撞:内存通过一个指针划分两边。分配内存相当于往空闲区域移动指针。(如Serial、ParNew等带compact过程的收集器)
空闲列表:虚拟机维护一个列表,记录哪些内存块是可用的。(如CMS收集器)
4)内存同步分配
分配空间的动作同步进行:CAS+失败重试
TLAB线程本地分配缓冲eden区
:thread local allocation buffer,哪个线程需要分配内存,先在TLAB上分配。TLAB用完 ,申请新的TLAB时才需要同步锁定。-XX:+/-UseTLAB
5)对象必要处理
初始化实例字段。
对象头的设置。(对象是哪个类的实例、Klass pointer、hashCode、gc年龄等)
6)clinit方法的执行。
2,对象的内存布局
1)对象内存划分:
对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。
2)对象头
Mark Word:对象自身运行时的数据,hashCode、gc标志、gc年龄、同步锁等。
Klass指针:指向类的元数据,通过这个指针确定是哪个类的实例。
3)对齐填充
仅仅起占位符的作用。对象的大小必须是8字节的倍数。
4)FieldsAllocationStyle字段分配策略
相同宽度的字段总是被分配到一起。longs/doubles、ints、shorts/chars、bytes/booleans、oopsOrdinary Object Pointers
3,对象的访问定位
1)使用对象。
通过栈上的reference数据来操作堆上的具体对象。(reference并没有定义如何去定位、访问堆中对象的具体位置,对象的访问方式取决于虚拟机实现。)
2)访问对象的方式
句柄访问:堆中单独划分内存作为句柄池。reference存储的就是对象的句柄地址(包含对象实例数据地址、对象类型数据地址)。
image.png
直接指针访问:reference存的是对象的地址,对象头中存储对象类型的地址。
image.png
4,逃逸分析(栈上分配)
1)分析对象的动态作用域。
方法逃逸:对象作为参数传递到其他方法。
线程逃逸:赋值给类变量、赋值给其他线程可以访问的实例变量。
2)栈上分配(Stack Allocation)
条件:对象不会逃逸到方法或者线程之外
同步消除:一个变量不会逃逸出线程,则对变量实施的同步措施可以消除。
标量:int、long等原始数据类型。
聚合量:对象
标量替换:对象不会被外部访问,对象拆散,使用成员变量替换,分配在栈上。
3)JVM参数jdk1.8默认开启
-XX:+DoEscapeAnalysis开启逃逸分析
-XX:+EliminateLocks开启锁消除
XX:+EliminateAllocations开启标量替换
网友评论