前言
Klass
A Klass provides:
language level class object (method dictionary etc.)
provide vm dispatch behavior for the object
both functions are combined into one c++ class
说白了 Klass 就是 Java 中的类在 JVM 中的内部表示
类层次结构
MetaspaceObj Metadata Klass
MetaspaceObj 类重载了 new 运算符来保证 Klass 分配在 metaspace(元空间,老版 JVM 叫持久代),
属性
Klass 类包含各种属性,详细描述 Java 类的类名,内存布局(大小),方法表,父子类关系 .etc
类名
Symbol* _name
父类
Klass* _super
子类
类可以有多个子类,通过 next-sibling 方式保存父子类关系
Klass* _subklassKlass* _next_sibling;
类加载器
Java 中类名和类加载器唯一标识了一个类,由同一个类加载器加载的类通过 _next_link 连接起来
oop
oop 是 ordinary object pointer 的缩写,oop 是 Java 对象的 JVM 内部表示. 如果没有打开CHECK_UNHANDLED_OOPS 预编译开关 oop 是指向 oopDesc 的指针,否则它是 oopDesc 指针的包装类
// oopsHierarchy.hpp#ifndef CHECK_UNHANDLED_OOPStypedef class oopDesc* oop;typedef class instanceOopDesc* instanceOop;typedef class arrayOopDesc* arrayOop;typedef class objArrayOopDesc* objArrayOop;typedef class typeArrayOopDesc* typeArrayOop; # elseclass oop { oopDesc* _o ...}#end
简单起见我们假定 CHECK_UNHANDLED_OOPS 为关闭状态,直接讨论 oopDesc 及其子类,这里先附上 oop 类层次图
oopDesc
oopDesc 类是虚拟机核心数据结构,字段和方法比较多,很多字段和方法与 JVM 运行期核心算法有关,所以一时看不懂也没啥,后续看到核心算法时再回过头来就会有 柳暗花明 的感觉
出于性能优化的考虑,oopDesc 类大量的 "小" 方法都被声明成 static inline
对象内存布局
对象内存布局分为 header(头部)和 fields(实例字段),header 又由 markOop(也是一个 oop)和 metadata 组成,metadata 存储者对象所属的类(KClass),这里使用了 union 来声明 metadata 是为了在 64 位机器上对对象指针进行压缩,64 位机器上指针类型占用 8 个字节,这种优化很多教材和文章都提到过
class oopDesc {private: volatile markOop _mark; union _metadata { Klass* _klass; narrowKlass _compressed_klass; } _metadata;}
oopDesc 类的 field_base 方法用于计算 类实例字段 的地址,offset 是偏移量
// oop.inline.hppinline void* field_base(int offset) const { return (void*)&((char*)this)[offset];}
因此可以推测,类实例字段存放在 oopDesc 中,紧跟着 oopDesc 本身占用的内存空间之后,这一点还可以从 header_size 方法证实,该方法返回对象头部(oopDesc 类)大小
// oop.inline.hpp// size of object header, aligned to platform wordSizestatic int header_size() { return sizeof(oopDesc)/HeapWordSize; }
instanceOopDesc
instanceOopDesc 是 oopDesc 的子类,和 oopDesc 基本没差,只是定义了一些 static 工具方法
// instanceOop.hpp// An instanceOop is an instance of a Java Class// Evaluating "new HashTable()" will create an instanceOop.class instanceOopDesc : public oopDesc { class instanceOopDesc : public oopDesc { public: // aligned header size. static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; } // If compressed, the offset of the fields of the instance may not be aligned. static int base_offset_in_bytes() { // offset computation code breaks if UseCompressedClassPointers // only is true return (UseCompressedOops && UseCompressedClassPointers) ? klass_gap_offset_in_bytes() : sizeof(instanceOopDesc); } static bool contains_field_offset(int offset, int nonstatic_field_size) { int base_in_bytes = base_offset_in_bytes(); return (offset >= base_in_bytes && (offset-base_in_bytes) < nonstatic_field_size * heapOopSize); }};}
arrayOopDesc
arrayOopDesc 是所有数组对象的基类,它的对象头除了 oopDesc 中声明的 markOop, Klass* 之外多了个 length 用于描述数组元素个数,聪明的你肯定猜到了,和实例字段一样,数组中的元素挨个存储在 头部 之后
length 和 set_length 方法用于获取和设置数组长度
int length() const { return *(int*)(((intptr_t)this) + length_offset_in_bytes()); } void set_length(int length) { *(int*)(((intptr_t)this) + length_offset_in_bytes()) = length; }
length_offset_in_bytes 用于计算 length 字段在对象内存布局中的偏移量,代码中的注释说明了为啥要这么干
// The _length field is not declared in C++. It is allocated after the // declared nonstatic fields in arrayOopDesc if not compressed, otherwise // it occupies the second half of the _klass field in oopDesc. static int length_offset_in_bytes() { return UseCompressedClassPointers ? klass_gap_offset_in_bytes() : sizeof(arrayOopDesc); }
objArrayOopDesc
typeArrayOopDesc
jhsdb
编译 openjdk 代码之后,在 jdk/bin 目录下有个实用程序 jhsdb,可以用来查看 hotspot 内部信息
我们写个简单的 Main 类,该类有两个字段 field1 和 field2
public class Main { private int field1; private int field2; public static void main(String[] args) throws Exception { Main main = new Main(); while (true) { Thread.currentThread().sleep(3000); } }}
使用编译出来的 openjdk 里面的 java 程序运行该类,记下进程号
# java Main &[1] 11780
clhsdb
启动 jhsdb,通过 clhsdb 选项选择使用 command line debugger
# jhsdb clhsdb# hsdb>
通过 help 命令可以查看支持的 命令列表
# hsdb> help assert true | false attach pid | exec core buildreplayjars [ all | app | boot ] | [ prefix ] detach dis address [length] disassemble address dumpcfg { -a | id } dumpcodecache dumpideal { -a | id } dumpilt { -a | id } dumpreplaydata { | -a |} echo [ true | false ] ...
我们使用 attach 命令 attach 到刚才那个 11780 进程(注意,必须使用 root 帐号执行该操作)
#hsdb> attach 11780Attaching to process 11780, please wait...hsdb>
可能会有一些关于 js engine 警告,先忽略,我们先查看一下 Main 符号定义
hsdb>symboltable Mainsun.jvm.hotspot.oops.Symbol@0x00007fe7c446a720
使用 jstack 查看线程
#hsdb> jstackThread 11785: (state = BLOCKED) - java.lang.Thread.sleep(long) @bci=0 (Compiled frame; information may be imprecise) - Main.main(java.lang.String[]) @bci=15, line=10 (Interpreted frame)
使用 where 查看 stack trace
#hsdb> where 11785Thread 11785 Address: 0x00007fe7c4019000Java Stack Trace for mainThread state = BLOCKED - public static native void sleep(long) @0x00007fe7b4232bc8 @bci = 0, pc = 0x00007fe7bcadedb8 (Compiled; information may be imprecise) - public static void main(java.lang.String[]) @0x00007fe7b471fb00 @bci = 15, line = 10, pc = 0x00007fe7b500b4a3 (Interpreted)
使用 print 查看 0x00007fe7b471fb00 代码
public static void main(java.lang.String[]) @0x00007fe7b471fb00Holder Classpublic class Main @0x00000007c0085030Checked Exception(s)public class java.lang.Exception @0x00000007c00030d0Bytecodeline bci bytecode 8 0 new #2 [Class Main] 8 3 dup 8 4 invokespecial #3 [Method void()] of public class Main @0x00000007c0085030 8 7 astore_1 10 8 invokestatic #4 [Method java.lang.Thread currentThread()] of public class java.lang.Thread @0x00000007c00068b0 10 11 pop 10 12 ldc2_w #5 10 15 invokestatic #7 [Method void sleep(long)] of public class java.lang.Thread @0x00000007c00068b0 10 18 goto 8 Constant PoolConstant Pool of [public class Main @0x00000007c0085030] @0x00007fe7b471f860
hsdb
启动 jhsdb,hsdb 选项选择使用 ui debugger,界面比较粗糙~~~
# jhsdb hsdb
选择 File 菜单中的 Attach to hotspot process,输入 PID 11780
选择 Tools 菜单中的 Object Histogram,打开 heap 中的 object 列表,在搜索框中输入 Main 并查找
双击 Main item,点击底部的 inspect 按钮查看 Main 对象对应 oop,可以很清楚的看到对象布局
Handle
可以将 Handle 类理解成访问对象的一个 "句柄",handles.hpp 文件头部的注释说明了为什么要增加这一层间接访问:
In order to preserve oops during garbage collection, they should beallocated and passed around via Handles within the VM. A handle issimply an extra indirection allocated in a thread local handle area
垃圾回收时对象可能被移动(对象地址发生改变),通过 handle 访问对象可以对使用者屏蔽垃圾回收细节
使用 Handle 的示例:
oop obj = ...;Handle h1(obj); // allocate new handleHandle h2(thread, obj); // faster allocation when current thread is knownHandle h3; // declare handle only, no allocation occurs..h3 = h1; // make h3 refer to same indirection as h1oop obj2 = h2(); // get handle valueh1->print();
Handle 实现和 "智能指针" 类似,主要是通过运算符重载:
// handles.hpp// General accessoop operator () () const { return obj(); }oop operator -> () const { return non_null_obj(); }bool operator == (oop o) const { return obj() == o; }bool operator == (const Handle& h) const { return obj() == h.obj(); }