问题:
1.解释一下对象的创建过程?
2.对象在内存中的存储布局?Object o = new Object()在内存中占用多少字符?
3.对象头具体包括什么?
4.对象怎么定位?
1.解释一下对象的创建过程?
T t = new T();
首先创建对象的时候,会先检查对象的class类有没有加载过,如果没加载过就执行类加载过程
-
class loading(加载)
- 通过一个类的全限定名来获取类的二进制字节流
并未指定总哪获取,怎么获取。所以字节流可以是存储在硬盘上的文件,可以是运行时动态生成的二进制字节流,可以是有其他文件生成的(JSP对应的class文件)等等 - 将字节流代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的Class对象,作为方法区这个类的访问入口
- 通过一个类的全限定名来获取类的二进制字节流
-
class linking(verification、preparation、resolution)
-
Verification (验证)
校验装载的class文件是否符合JVM规范
-
Preparation(准备)
将class文件的静态变量赋默认值
如:
static int i = 8 ;
这里会给i赋值0,而不是赋值8。
-
Resolution(解析)
- 将类、方法、属性等符号引用解析为直接引用
对class文件常量池中各种符号引用进行解析,转成指针,偏移量等内存地址的直接引用
-
-
class initializing(初始化)
- 给类静态变量设置初始值,同时执行静态语句块.
-
申请对象内存
-
成员变量赋默认值
-
给类成员变量 比如 int i =8;
将i赋值为0; i=0;
-
-
调用构造方法
-
成员变量顺序赋初始值
i =8;
-
执行构造方法
super();一旦调用构造方法就先会执行父类初始化过程
-
2.对象在内存中的存储布局?Object o = new Object()在内存中占用多少字符?
-
对象的大小以及内存布局与虚拟机的实现和设置有很大关系,所以先查看虚拟机的默认配置信息
-
观察虚拟机配置
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize 初始的堆大小-XX:MaxHeapSize 最大的堆大小
-XX:+UseComparessedClassPointers 和对象头有关系
-XX:+UseCOmparessedOops 和对象内存布局有关系
-XX:+UseParallelGC 和GC有关系
-
对象的内存布局可以分为两种对象普通对象和数组对象
普通对象
1.对象头:在虚拟机中称为(markword),8个字节
2.ClassPointer指针(类指针,指向这个对象对应的T.class对象):
-XX:+UseCompressedClassPointers 开启的话是4个字节,不开启则为8个字节
3.实例数据(成员变量)
引用类型:-XX:+UseCOmpressedOops 开始的话4个字节,不开启则为8个字节
基本数据类型:
4.Padding对齐,8的倍数。
假如这个对象前面几项占用的字节为15则会补1个字节,则这个对象占用16字节
数组对象(比普通对象多一个数组长度)
1.markword 8个字节
2.ClassPointer指针 4/8个字节
3.数组长度:4个字节
3.数组数据
4.Padding对齐,8的倍数。
假如这个对象前面几项占用的字节为15则会补1个字节,则这个对象占用16字节
-
实验——观察对象的大小
使用技术:java Agent
JavaAgent 是JDK 1.5 以后引入的,也可以叫做Java代理。
JavaAgent 是运行在 main方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行 premain 方法然后再执行 main 方法。
class文件在loading到内存中的时候,可以通过javaAgent做代理,抓取class的二进制字节码进行操作。
1.新建项目ObjectSize
-
新建项目ObjectSize (1.8)
-
创建文件ObjectSizeAgent
package com.ls.jvm.agent; import java.lang.instrument.Instrumentation; /** * Created by 刘绍 on 2020/2/16. */ public class ObjectSizeAgent { private static Instrumentation inst; public static void premain(String agentArgs, Instrumentation _inst) { inst = _inst; } public static long sizeOf(Object o) { return inst.getObjectSize(o); } }
-
src目录下创建META-INF/MANIFEST.MF
Manifest-Version: 1.0 Created-By: mashibing.com Premain-Class: com.mashibing.jvm.agent.ObjectSizeAgent
注意Premain-Class这行必须是新的一行(回车 + 换行),确认idea不能有任何错误提示
-
打包jar文件
-
在需要使用该Agent Jar的项目中引入该Jar包 project structure - project settings - library 添加该jar包
-
运行时需要该Agent Jar的类,加入参数:
-javaagent:C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar
-
如何使用该类:
import com.ls.jvm.agent.ObjectSizeAgent; public class SizeOfAnObject { public static void main(String[] args) { System.out.println(ObjectSizeAgent.sizeOf(new Object()));//16 /*对象头8个字节+类指针4个字节,由于默认开启了-XX:+UseComparessedClassPointers 所以是4个字节,成员变量没有所以对象字节为8+4=12 最后Padding对齐,最后结果为16字节*/ System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));//16 /*对象头8个字节+类指针4个字节,由于默认开启了-XX:+UseComparessedClassPointers 所以是4个字节,数组长度4个字节,成员变量没有所以对象字节为8+4+4=16。结果是8的倍数所以 不需要最后Padding对齐,最后结果为16字节*/ /*在执行main方法的时候加入参数 -XX:-UseComparessedClassPointers 关闭类指针压缩 ObjectSizeAgent.sizeOf(new Object()) 为16字节 对象头8个字节+类指针8个字节=16字节 ObjectSizeAgent.sizeOf(new int[] {}) 对象头8个字节+类指针8个字节+数组长度4个字节+Padding对齐4个字节 = 24字节*/ System.out.println(ObjectSizeAgent.sizeOf(new P()));//32 //对象头8个字节+类指针4个字节+4+4+4+1+1+4+1+Padding对齐1个字节 = 24字节 } private static class P { //8 _markword //4 _oop指针 //-XX:+UseCOmparessedOops 成员变量类型引用指针。默认开启4字节,不开启为8字节 int id; //4 String name; //4 int age; //4 byte b1; //1 byte b2; //1 Object o; //4 byte b3; //1 } }
-
Hotspot开启内存压缩的规则(64位机)
- 4G以下,直接砍掉高32位
- 4G - 32G,默认开启内存压缩 ClassPointers Oops
- 32G,压缩无效,使用64位 内存并不是越大越好(-)
-
3.对象头具体包括什么?
包括存储对象自身的运行时数据:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
1.其中可以看到不管32位还是64位中 GC的分代年龄只有4bit,4bit最大的数字为15。所以新生代进入老年代中最大的年龄限制就是15
2.当一个对象计算过identityHashCode之后,不能进入偏向锁状态
参考资料:
https://cloud.tencent.com/developer/article/1480590
https://cloud.tencent.com/developer/article/1484167
https://cloud.tencent.com/developer/article/1485795
https://cloud.tencent.com/developer/article/1482500
4.对象怎么定位?
(深入java虚拟机第二版 第48页)
建立对象是为了使用对象,我们通过栈上的reference数据来操作堆上的具体对象。
由于reference类型在java虚拟机规范中只规定了一个指向对象的引用,并没有定义引用应该通过什么方式定位、访问堆中的对象具体位置,所以对象的访问方式取决于虚拟机的实现。
主流的访问方式有两种,HotSpot采用的是直接访问
1.句柄访问
好处:reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不会改变
2.指针访问
好处:访问速度更快,它节省了一次指针定位的时间开销