1 准备
1.1 FBI WARNING
文章异常啰嗦且绕弯。
1.2 版本
使用 openjdk 24 为跟踪的源码。
fork 仓库:https://github.com/openjdk/jdk/
2 源码追踪
2.1 oopDesc
在 JVM 中,Java 对象的最高层级抽象是 oopDesc。
代码路径在 hotspot/share/oops/oop.hpp 中。
class oopDesc {
friend class VMStructs;
friend class JVMCIVMStructs;
private:
// 对象头
volatile markWord _mark;
// class 信息
union _metadata {
// 指向一个 klass 对象,就是一个 Java 中的 Class 对象
Klass* _klass;
// 压缩指针
narrowKlass _compressed_klass;
} _metadata;
// 其它方法先省略
}
friend class 是 cpp 中的语法,VMStructs 和 JVMCIVMStructs 可以访问 oopDesc 中的私有变量,此处暂不展开。
当 Java 代码需要使用到 Class 相关信息时候,JVM 会调用 klass() 方法来进行一次筛选,此方法在 hotspot/share/oops/oop.inline.hpp 中:
Klass* oopDesc::klass() const {
switch (ObjLayout::klass_mode()) {
// ObjLayout 如果显示开启了对象头压缩,直接获取 markword 里的 klass 对象
case ObjLayout::Compact:
return mark().klass();
// ObjLayout 如果显示开启了指针压缩,通过 _compressed_klass 去解压内存地址,并获取 klass 对象
case ObjLayout::Compressed:
return CompressedKlassPointers::decode_not_null(_metadata._compressed_klass);
// 都没开,则直接获取 _klass
default:
return _metadata._klass;
}
}
2.2 ObjLayout
ObjLayout 是一个枚举,代码在 hotspot/share/oops/objLayout.hpp 中:
class ObjLayout {
public:
enum Mode {
// +UseCompactObjectHeaders (implies +UseCompressedClassPointers)
Compact,
// +UseCompressedClassPointers (-UseCompactObjectHeaders)
Compressed,
// -UseCompressedClassPointers (-UseCompactObjectHeaders)
Uncompressed,
// Not yet initialized
Undefined
};
// 其它省略
}
ObjLayout::klass_mode() 方法在 hotspot/share/oops/objLayout.inline.hpp 中:
inline ObjLayout::Mode ObjLayout::klass_mode() {
#ifdef ASSERT
assert(_klass_mode != Undefined, "KlassMode not yet initialized");
if (UseCompactObjectHeaders) {
assert(_klass_mode == Compact, "Klass mode does not match flags");
} else if (UseCompressedClassPointers) {
assert(_klass_mode == Compressed, "Klass mode does not match flags");
} else {
assert(_klass_mode == Uncompressed, "Klass mode does not match flags");
}
#endif
#ifdef _LP64
return _klass_mode;
#else
return Uncompressed;
#endif
}
ifdef _LP64 用于判断当前操作系统是否是 64 位操作系统,JVM 的压缩功能主要针对的是 64 位操作系统,对于 32 位操作系统基本是没有优化空间的。
通过注释可以知道,在设置了压缩对象头的时候,默认也等同于开启了压缩类指针,此时 ObjLayout 是 Compact;没有开启压缩对象头,但是设置了压缩类指针的时候,ObjLayout 是 Compressed;都没有开的时候,是 Uncompressed。
2.3 metadata
回到 oopDesc 中的 _metadata:
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
根据上面解读的 oopDesc.klass() 方法,如果 JVM 启动的时候没开启类指针压缩,则使用 _klass 对象,如果开启了类指针压缩,则使用 _comporessed_klass 对象。
_compressed_klass 是一个 narrorKlass 类型的对象,在 hotspot/share/oops/compressedKlass.hpp 中,可以看到:
// If compressed klass pointers then use narrowKlass.
typedef juint narrowKlass;
这个申明说明了,narrowKlass 类型本质上等同于 juint 类型,而在 hotspot/share/utilities/globalDefinitions.hpp 中,可以看到:
typedef uint32_t juint;
所以可以知道,juint 类型本质上就是 uint32_t 类型,在 64 位操作系统上是 4 字节。
narrowKlass 等同于 uint32_t,所以 _compressed_klass 在 64 位系统中是一个 4 字节的数字;而 Klass* 是一个引用,占内存 8 字节。
所以本质上 JVM 通过使用一个 4 字节整数类型去代替了一个引用类型来节约了 4 字节的内存。
2.4 markWord
markWord 的代码路径在 hotspot/share/oops/markWord.hpp 中。
class markWord {
private:
uintptr_t _value;
// 其它方法先省略
}
可以看到 markWord 的本质是一个 uintptr_t 类型的值,在 64 位的操作系统中是 8 字节。
根据上面解读的 oopDesc.klass() 方法,如果 JVM 启动了对象头压缩,则会默认调用 mark().klass() 方法。
对 _mark 的基础操作方法在 hotspot/share/oops/oop.inline.hpp 中:
// 获取 markWord 对象,这个过程是原子化的
markWord oopDesc::mark() const {
return Atomic::load(&_mark);
}
// 获取一个初始化的 markWord 对象
markWord oopDesc::prototype_mark() const {
if (UseCompactObjectHeaders) {
// 当开启对象头压缩的情况下,获取 klass 中的 _prototype_header 对象,作为统一初始化对象头
return klass()->prototype_header();
} else {
// 这里会创建一个新的 markWord 对象
return markWord::prototype();
}
}
// 初始化一个 markWord
void oopDesc::init_mark() {
// 获取一个初始化的 markWord,然后 set 进当前对象的 _mark 对象里
set_mark(prototype_mark());
}
// 原子化的将 _mark 所指向的对象替换成一个新的 markWord
void oopDesc::set_mark(markWord m) {
Atomic::store(&_mark, m);
}
markWord::prototype() 在 hotspot/share/oops/markWord.hpp 中:
// 这里参数没列全,涉及到的静态对象有点多
static const uintptr_t no_hash_in_place = (uintptr_t)no_hash << hash_shift;
static const uintptr_t no_lock_in_place = unlocked_value;
static const uintptr_t unlocked_value = 1;
static const uintptr_t no_hash = 0 ;
static const int hash_shift = age_shift + age_bits + unused_gap_bits;
static markWord prototype() {
return markWord( no_hash_in_place | no_lock_in_place );
}
这里 JVM 用了一个数学计算来获取了一个初始化的 markWord,具体不展开了。
markWord 操作 klass 的方法在 hotspot/share/oops/markWord.inline.hpp 中:
Klass* markWord::klass() const {
#ifdef _LP64
assert(UseCompactObjectHeaders, "only used with compact object headers");
return CompressedKlassPointers::decode_not_null(narrow_klass());
#else
ShouldNotReachHere();
return nullptr;
#endif
}
narrowKlass markWord::narrow_klass() const {
#ifdef _LP64
assert(UseCompactObjectHeaders, "only used with compact object headers");
return narrowKlass(value() >> klass_shift);
#else
ShouldNotReachHere();
return 0;
#endif
}
前面说过,narrowKlass 本质上是一个 4 字节的数字,这里 klass() 方法本质上也就是在一个 4 字节数字里寻址。
参考
https://www.cnblogs.com/RDaneelOlivaw/p/13970242.html
https://blog.csdn.net/dshf\_1/article/details/103422963
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。