上一篇:【深入理解JVM】3、CPU储存器+MESI+CPU伪共享+CPU乱序问题及代码论证【面试必备】
问题1、请解释一下对象的创建过程?
- class loading
- class linking
- verification
- preparation
- resolution
- class initializing 静态初始化的过程
- 申请对象内存
- 成员变量赋默认值
- 调用构造方法<init>
- 成员变量顺序赋初始值
- 执行构造方法语句:首先去调用super
问题2、对象在内存中的存储布局?
1、先说一下观察虚拟机配置的命令:
java -XX:+PrintCommandLineFlags -version
idea的设置:
在虚拟机执行命令的效果:
C:\Users\zhouwei>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266984512 初始的堆大小
-XX:MaxHeapSize=4271752192 最大的堆大小
-XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers:(默认时压缩的,把+号改成-号就不再压缩)
-XX:+UseCompressedOops(oops):普通对象的指针 ordinary object pointers比如引用的string见下
-XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
2、普通对象
注释:有些地方是Klass Pointer有些又是
内存里的一个Java对象分为三部分:对象头,实例数据,对齐。
- 对象头:markword 8Bytes 用于标记锁信息、GC信息、IdentityHashCode等。
- class Pointer 类指针:jvm开启内存压缩(-XX:+UseCompressedClassPointer),4字节。不开启,8字节。默认开启,用于标记该对象是哪个Class的实例 例如:Object.class。
- 实例数据(instance data):包括对象的所有成员变量,大小由各个成员变量决定,如果没有成员变量,则这一块为空,基本类型 (int,float 4字节 long,double 8字节,char,short 2字节,byte 1字节 ,boolean 实际大小1bit 占用1字节)。
- 引用类型:-XX:+UseCompressedOops 为4字节 不开启为8字节 , Oops Ordinary Object Pointers
- Padding对齐:8的倍数(因为读取的时候不是按直接读的,是读的块,一般是8个字节的倍数,这样效率会更高)
3、数组对象
- 对象头:markword 8Bytes 用于标记锁信息、GC信息、IdentityHashCode等
- class Pointer 类指针:jvm开启内存压缩(-XX:+UseCompressedClassPointer),4字节。不开启,8字节。默认开启,用于标记该对象是哪个Class的实例 例如:Object.class
- 数组长度:4字节 标记数组有多少个元素
- 数组数据(instance data):根据数组类型m和长度n而定,长度为m*n
如果元素为基本类型,比如byte/boolean/short/char/int/long/double,则m为对应的长度;
如果元素为数组, m是4字节的引用
如果数组长度为0,这一块为空 - padding对齐:一个对象占用的字节数必须是8的倍数,不足的用padding对齐
实验:利用java agent(class文件到内存之间的代理,这个代理得自己去实现)的机制。下面补充了种用JOL工具的,比较简单。
1、创建文件ObjectSizeAgent(类似到内存前有个代理)
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);
}
}
2、src目录下创建META-INF/MANIFEST.MF
这里可以只要Premain-Class: com.mashibing.jvm.agent.ObjectSizeAgent
Manifest-Version: 1.0
Created-By: mashibing.com
Premain-Class: com.mashibing.jvm.agent.ObjectSizeAgent
3、打包jar文件
4、在需要使用该Agent Jar的项目中引入该Jar包 project structure - project settings - library 添加该jar包
5、运行时需要该Agent Jar的类,加入参数:
执行用哪个jar文件当作代理来运行我的虚拟机:
-javaagent:C:\work\ijprojects\ObjectSize\out\artifacts\ObjectSize_jar\ObjectSize.jar
6、如何使用该类:
测出来 object:16个字节 object分析:
对象头是8个字节,本来应该是8个字节(64位的)因为默认打开时压缩的,被压缩成4个字节(class_pointer ),
所有整个object对象时8+4=12个字节,测出来时16个字节,因为后面有个padding(对齐)4个字节。
8+4+padding
数组分析:
对象头8个字节+ class_pointer (网上很多把这里写成OOPS,这是错误的)压缩4个字节,长度4个字节 =16个字节。
-XX:+UseCompressedClassPointers把+号改成-号不压缩了,数组就变成了24个字节。
对象头8个字节+class_pointer 没压缩 8个字节 +长度4个字节+paddding 4个字节 =24个字节
注释:A:-XX:+UseCompressedOops(oops):普通对象的指针 ordinary object pointers比如引用的String见下
B:-XX:+UseCompressedClassPointers:(默认时压缩的,把+号改成-号就不再压缩)
public class T03_SizeOfAnObject {
public static void main(String[] args) {
// ObjectSizeAgent这个jar包是自己生成的。
System.out.println(ObjectSizeAgent.sizeOf(new Object()));
System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));
System.out.println(ObjectSizeAgent.sizeOf(new P()));
}
// 一个Object占多少个字节
// -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
// Oops = ordinary object pointers
private static class P {
//8 _markword
//4 _class pointer
int id; //4
String name; //4
int age; //4
byte b1; //1
byte b2; //1
Object o; //4
byte b3; //1
}
}
Hotspot开启内存压缩的规则(64位机)
1. 4G以下,直接砍掉高32位2. 4G - 32G,默认开启内存压缩 ClassPointers Oops3. 32G,压缩无效,使用64位 内存并不是越大越好(^-^)
3、对象头包括什么?
1.8的实现,C++文件,去看Hotspot源码。(很复杂)
知道几点就行了(听过就行了):
关于锁状态详情可以参考这篇文章:【Java对象解析】不得不了解的对象头
HotSpot虚拟机的对象头(Object Header)包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。
对象需要存储的运行时数据很多,其实已经超出了32、64位Bitmap结构所能记录的限度,但是对象头信息是与对象自身定义的数据无关的额 外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机 中对象未被锁定的状态下,Mark Word的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志 位,1Bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示。
对象头的另外一部分是类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。
锁标志位与是否偏向锁对应到唯一的锁状态
锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。
偏向锁:(启用参数-XX:+UseBiasedLocking,这是JDK 1.6的默认值)为了让线程获得锁的代驾更低而引入了偏向锁,那么,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程。那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步,退出同步也,无需每次加锁解锁都去CAS更新对象头,如果不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候需要锁膨胀为轻量级锁,才能保证线程间公平竞争锁。乐观锁。
轻量级锁:还没有涉及到内核。获取轻量锁的过程与偏向锁不同,竞争锁的线程首先需要拷贝对象头中的Mark Word到帧栈的锁记录中。拷贝成功后使用CAS操作尝试将对象的Mark Word更新为指向当前线程的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁。如果更新失败,那么意味着有多个线程在竞争。
- 轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁
- 每次进入退出同步块都需要CAS更新对象头
- 争夺轻量级锁失败时,自旋尝试抢占锁
重量级锁:有涉及到内核。消耗的资源比较高。重量级锁的加锁、解锁过程和轻量级锁差不多,区别是:竞争失败后,线程阻塞,释放锁后,唤醒阻塞的线程,不使用自旋锁,不会那么消耗CPU,所以重量级锁适合用在同步块执行时间长的情况下。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking来禁用偏向锁。
对象的hashCode平时不是在25bit那,只有调用的时候才会被标记。
hashcode还比较特殊:(没搞懂)
- 对象的hashcode方法被重写:按原始内容计算的hashcode,重写过的hashcode方法计算的结果不会存在这里。
- 对象的hashcode没有被重写:那么默认是调用os:random产生的hashcode,可以通过System.identityHashCode获取;os:random产生hashcode的规则为:next——rand=(16807seed)mod(2*31-1),因此可以使用31位存储另外一旦生成了hashcode,JVM会将其记录在markword中。称之为identityHashCode。
什么时候回产生hashcode?
- 调用未重写的hashcode方法以及system.identityHashCode的时候。
有两个位置是标记GC的。
为什么GC年龄默认为15?
- 因为分代年龄头只有4位,最大为15.(有人说这个数能调,扯淡的)
markword 64位 8个字节
只找到32位的图
当一个对象计算过identityHashCode之后,不能进入偏向锁状态?
因为计算过hashcode,前25位就已经被占用了,所以无法进入偏向锁状态。
感兴趣可以参考:
4、对象定位
参考《深入理解JAVA虚拟机》
T t = new T();t是怎么找到实际new出来的对象的?
1、句柄池:
- 效率相对较低,稳定
- 但是在GC(三色标记算法)垃圾回收的时候效率会比较高。
- 使用句柄访最大的好处是reference中存储着稳定的句柄地址,当对象移动之后(垃圾收集时移动对象是非常普遍的行为),只需要改变句柄中的对象实例地址即可,reference不用修改。
2、直接指针:
- 访问速度快,它减少了一次指针定位的时间开销,由于java是面向对象的语言,在开发中java对象的访问非常的频繁,因此这类开销积少成多也是非常可观的,反之则提升访问速度。
补充:利用JOL工具JOL工具及其分析对象在JVM的大小和分布代替之前的java agent方式。
JOL全称为Java Object Layout,是分析JVM中对象布局的工具,该工具大量使用了Unsafe、JVMTI来解码布局情况,所以分析结果是比较精准的。通常分析java对象的大小需要人工按照Java基础数据类型大小及内容大小估算出缓存对象的大概堆占用,但是麻烦还不准。而OpenJDK,提供了JOL包,可以帮我们在运行时计算某个对象的大小,是非常好的工具。
应用:分析对象在JVM的大小和分布
依赖:
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
/**
* @author zhouwei
* @date
*/
public class JolDemo {
static Object generate() {
Map<String, Object> map = new HashMap<>();
map.put("a", new Integer(1));
map.put("b", "b");
map.put("c", new Date());
for (int i = 0; i < 10; i++) {
map.put(String.valueOf(i), String.valueOf(i));
}
return map;
}
static void print(String message) {
System.out.println(message);
System.out.println("-------------------------");
}
public static void main(String[] args) {
Object obj = generate();
// 查看对象内部信息
print("查看对象内部信息:"+ClassLayout.parseInstance(obj).toPrintable());
// 查看对象外部信息:包括引用的对象
print("查看对象外部信息:包括引用的对象"+GraphLayout.parseInstance(obj).toPrintable());
// 查看对象占用空间总大小
print("查看对象占用空间总大小size : " + GraphLayout.parseInstance(obj).totalSize());
}
}
打印结果:
查看对象内部信息:java.util.HashMap object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) a8 37 00 f8 (10101000 00110111 00000000 11111000) (-134203480)
12 4 java.util.Set AbstractMap.keySet null
16 4 java.util.Collection AbstractMap.values null
20 4 int HashMap.size 13
24 4 int HashMap.modCount 13
28 4 int HashMap.threshold 24
32 4 float HashMap.loadFactor 0.75
36 4 java.util.HashMap.Node[] HashMap.table [null, (object), (object), (object), null, null, null, null, null, null, null, null, null, null, null, null, (object), (object), (object), (object), (object), (object), (object), (object), (object), (object), null, null, null, null, null, null]
40 4 java.util.Set HashMap.entrySet null
44 4 (loss due to the next object alignment)
Instance size: 48 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
-------------------------
查看对象外部信息:包括引用的对象java.util.HashMap@10dba097d object externals:
ADDRESS SIZE TYPE PATH VALUE
76b7302b0 48 java.util.HashMap (object)
76b7302e0 24 java.lang.String .table[1].key (object)
76b7302f8 24 [C .table[1].key.value [a]
76b730310 2112 (something else) (somewhere else) (something else)
76b730b50 16 java.lang.Integer .table[1].value 1
76b730b60 80 (something else) (somewhere else) (something else)
76b730bb0 32 java.util.HashMap$Node .table[1] (object)
76b730bd0 24 java.lang.String .table[2].key (object)
76b730be8 24 [C .table[2].key.value [b]
76b730c00 32 java.util.HashMap$Node .table[2] (object)
76b730c20 24 java.lang.String .table[3].key (object)
76b730c38 24 [C .table[3].key.value [c]
76b730c50 70760 (something else) (somewhere else) (something else)
76b7420b8 24 java.util.Date .table[3].value (object)
76b7420d0 32 java.util.HashMap$Node .table[3] (object)
76b7420f0 24 [C .table[16].key.value [0]
76b742108 24 java.lang.String .table[16].key (object)
76b742120 24 [C .table[16].value.value [0]
76b742138 24 java.lang.String .table[16].value (object)
76b742150 32 java.util.HashMap$Node .table[16] (object)
76b742170 24 [C .table[17].key.value [1]
76b742188 24 java.lang.String .table[17].key (object)
76b7421a0 24 [C .table[17].value.value [1]
76b7421b8 24 java.lang.String .table[17].value (object)
76b7421d0 32 java.util.HashMap$Node .table[17] (object)
76b7421f0 24 [C .table[18].key.value [2]
76b742208 24 java.lang.String .table[18].key (object)
76b742220 24 [C .table[18].value.value [2]
76b742238 24 java.lang.String .table[18].value (object)
76b742250 32 java.util.HashMap$Node .table[18] (object)
76b742270 24 [C .table[19].key.value [3]
76b742288 24 java.lang.String .table[19].key (object)
76b7422a0 24 [C .table[19].value.value [3]
76b7422b8 24 java.lang.String .table[19].value (object)
76b7422d0 32 java.util.HashMap$Node .table[19] (object)
76b7422f0 24 [C .table[20].key.value [4]
76b742308 24 java.lang.String .table[20].key (object)
76b742320 24 [C .table[20].value.value [4]
76b742338 24 java.lang.String .table[20].value (object)
76b742350 32 java.util.HashMap$Node .table[20] (object)
76b742370 24 [C .table[21].key.value [5]
76b742388 24 java.lang.String .table[21].key (object)
76b7423a0 24 [C .table[21].value.value [5]
76b7423b8 24 java.lang.String .table[21].value (object)
76b7423d0 32 java.util.HashMap$Node .table[21] (object)
76b7423f0 24 [C .table[22].key.value [6]
76b742408 24 java.lang.String .table[22].key (object)
76b742420 24 [C .table[22].value.value [6]
76b742438 24 java.lang.String .table[22].value (object)
76b742450 32 java.util.HashMap$Node .table[22] (object)
76b742470 24 [C .table[23].key.value [7]
76b742488 24 java.lang.String .table[23].key (object)
76b7424a0 24 [C .table[23].value.value [7]
76b7424b8 24 java.lang.String .table[23].value (object)
76b7424d0 32 java.util.HashMap$Node .table[23] (object)
76b7424f0 24 [C .table[24].key.value [8]
76b742508 24 java.lang.String .table[24].key (object)
76b742520 24 [C .table[24].value.value [8]
76b742538 24 java.lang.String .table[24].value (object)
76b742550 32 java.util.HashMap$Node .table[24] (object)
76b742570 24 [C .table[25].key.value [9]
76b742588 24 java.lang.String .table[25].key (object)
76b7425a0 24 [C .table[25].value.value [9]
76b7425b8 24 java.lang.String .table[25].value (object)
76b7425d0 32 java.util.HashMap$Node .table[25] (object)
76b7425f0 144 [Ljava.util.HashMap$Node; .table [null, (object), (object), (object), null, null, null, null, null, null, null, null, null, null, null, null, (object), (object), (object), (object), (object), (object), (object), (object), (object), (object), null, null, null, null, null, null]
-------------------------
查看对象占用空间总大小size : 1752