持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
01-Klass and OOP
HotSpot JVM 实现中用 Klass-OOP 模型来表示 Java 中的类和对象。
图 1. Klass 的继承体系
图 2. oopDesc 的继承体系
Klass 结构是 .class 文件的运行时结构; oopDesc 结构是 Object 对象的运行时结构。
oopDesc(对象头)中包含了两部分信息(更多详细的内容参考[1]):
- mark word,存储了哈希码、锁信息、GC元数据等。
- klass word,类信息。注:应该是指针,指向 Metaspace 中的类结构。
- possible alignment paddings,非必须
对象头后存储的是对象的实例数据。
图 3. oopDesc 布局示意图
JVM 中的普通对象表示为 instanceOopDesc,数组对象表示为 arrayOopDesc。两者在结构上的区别是,后者多了 4 个字节的长度信息。
02-Java Object Layout
在 32/64 位机器上,mark word 是有区别的(更多详细信息[1]):
32 bits:
--------
hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
64 bits:
--------
unused:25 hash:31 -->| unused_gap:1 age:4 biased_lock:1 lock:2 (normal object)
JavaThread*:54 epoch:2 unused_gap:1 age:4 biased_lock:1 lock:2 (biased object)
图 4. 32 bits mark word 可能的分配情况及其含义
图 5. 64 bits mark word 可能的分配情况及其含义
-
无锁 → 偏向锁 → 轻量级锁 → 重量级锁 升级过程(不可降级)?更多详细参考[1]
偏向锁获取:CAS 比较线程ID,同一线程再次获得锁的效率提高。
偏向锁获取失败时,说明有其他线程加入抢锁的队伍,在到达 safepoint 后获得偏向锁的线程被挂起,判断锁对象是否处于锁定状态,并据此决定撤销偏向锁或升级为轻量级锁。
轻量级锁(自旋锁):
借助工具查看 Java 对象内存布局?
- HSDB [1]
- jol [1, 2]
-
示例
public class SimpleInt { private int state; }# Running 64-bit HotSpot VM. # Objects are 8 bytes aligned. # Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] # Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] # ClassLayout.parseClass(SimpleInt.class).toPrintable() self.samson.example.jol.SimpleInt object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 16 (object header) N/A 16 4 int SimpleInt.state N/A 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total # ClassLayout.parseInstance(instance).toPrintable() self.samson.example.jol.SimpleInt object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 88 13 1a 1d (10001000 00010011 00011010 00011101) (488248200) 12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 16 4 int SimpleInt.state 0 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
-
03-对象创建过程
在所有会创建对象的场景中,该类的某个特定构造器方法会被调用。
- 创建类的实例时,会在堆上开辟空间存放所有的 instance variables,包括其超类声明的实例变量(也包括被子类 hide 的变量)。
- 实例变量被初始化为默认值
- 在新创建对象的引用被返回之前,调用特定的 constructor(先执行 instance initializers 和 instance variable initializers)
class Super {
Super() { printThree(); }
void printThree() { System.out.println("three"); }
}
class Test extends Super {
int three = (int)Math.PI; // That is, 3
void printThree() { System.out.println(three); }
public static void main(String[] args) {
Test t = new Test();
t.printThree();
}
}
// 输出
// 0
// 3
-
内存分配方式有哪些呢?
若堆是规整的,即使用过得放在一边,空闲未用的放在一边,在两者之间存在一个指针,称为分界指示器。分配方式就是指示器向空闲部分移动一定的大小。这种方式称为指针碰撞。
若堆不是规整的,空闲内存块列表记录在空闲列表中,分配方式为从空闲列表中取合适的大小分配给对象。
-
访问对象的方式有哪些呢?
通过句柄访问,栈中对象的引用指向的是句柄地址,句柄包含了实例数据与数据类型信息。对象移动时更方便。
直接访问,栈中对象的引用指向的是实例数据的地址。访问速度更高。
-
如何判断对象已经死亡?
引用计数,最直接,最简单,但不能解决循环引用问题,而且算法边界很多,不容易实现
可达性分析,从 GC Root 开始,根据引用关系向下搜索。GC Root 包括:栈中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中 JNI 引用的对象、JVM 内部的引用例如 Class 对象、所有被 synchronized 持有的对象、JMX 内部中注册的回调等等。
-
应用类型?更多信息参考 [1]
强引用
软引用
弱引用
虚引用
-
如何判断一个类不再使用?同时满足以下三个条件:
类的所有实例已被回收
类的加载器已被回收(通常难以达成,除非特殊设计目的存在,例如OSGI、JSP的重加载等)
类对应的 Class 对象不被应用,也不能通过反射访问这个类
历史文章推荐