简介
HotSpot VM是当前使用范围最广的Java虚拟机, 我们常规的Java开发通常都不会直接和底层内存打交道, 因为HotSpot VM已经帮我们把数据的解析,内存的分配(垃圾回收),类的加载等等事情都封装打包好了, 我们只需要处理我们关心的功能(编写并调用Java层的类, 反射等等), 不会关心底层的数据是如何传递和获取, 大大的方便了我们的日常开发, 甚至帮我们实现了非常好的跨平台能力, 在Windows上面写的Java程序在Linux和Mac上面同样可以稳定的运行, 那我们写的好的Java程序被编译成Class文件之后, 被加载的Class在底层内存中是如何存储的, 我们知道的注解, 常量池甚至方法在内存当中是什么样子的呢…
InstanceKlass
InstanceKlass是Java Class在JVM层面的内存表示, 包含了类在执行过程中需要的所有信息, 常用的如下:
字段名 | 作用 |
---|---|
_annotations | Annotations类型的指针, 保存该类 使用的所有注解 |
_array_klasses | 数组元素为该类的数组Klass指针 |
_constants | ConstantPool指针,该类的常量池 |
_minor_version | 此类的主版本号 |
_major_version | 此类的次版本号 |
_methods | 方法指针数组,类方法 |
类的注解
这里我们看看注解在内存的什么地方, 测试用例:
package com.example.springbootdemo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.annotation.Annotation;
@RestController
public class Test {
@GetMapping("test/an")
public void Getan() {
Class t = HelloController.class;
Annotation[] da = t.getDeclaredAnnotations();
if (da.length > 0){
for (Annotation _da : da) {
System.out.println("Annotation: " + _da);
}
}
}
}
这里我们可以看到类有一个注解RestController
, 方法Getan()
有一个注解GetMapping
;
InstanceKlass
结构的_annotations
字段保存了类的注解的指针, 我们通过sa-jar来查看一下:
启动sa-jar
:
java -cp "%JAVA_HOME%/lib/sa-jdi.jar" sun.jvm.hotspot.CLHSDB
attach程序并查找com.example.springbootdemo.Test
的InstanceKlass
地址:
$ jps
4532 Launcher
34744 SpringbootDemoApplication
14540
35420 Jps
$ java -cp "%JAVA_HOME%/lib/sa-jdi.jar" sun.jvm.hotspot.CLHSDB
hsdb> attach 34744
Attaching to process 34744, please wait...
hsdb> class com.example.springbootdemo.Test
com/example/springbootdemo/Test @0x00000007c02931c8
hsdb>
通过inspect
命令来解析InstanceKlass
结构:
hsdb> inspect 0x00000007c02931c8
Type is InstanceKlass (size of 440)
juint Klass::_super_check_offset: 48
Klass* Klass::_secondary_super_cache: Klass @ null
Array<Klass*>* Klass::_secondary_supers: Array<Klass*> @ 0x0000000025a20088
Klass* Klass::_primary_supers[0]: Klass @ 0x00000007c0000f28
oop Klass::_java_mirror: Oop for java/lang/Class @ 0x00000005c55b5fd8 Oop for java/lang/Class @ 0x00000005c55b5fd8
jint Klass::_modifier_flags: 1
Klass* Klass::_super: Klass @ 0x00000007c0000f28
Klass* Klass::_subklass: Klass @ null
jint Klass::_layout_helper: 16
Symbol* Klass::_name: Symbol @ 0x000000002be58cd0
AccessFlags Klass::_access_flags: 538968097
markOop Klass::_prototype_header: 5
Klass* Klass::_next_sibling: Klass @ 0x00000007c0292fb8
u8 Klass::_trace_id: 268238848
Klass* InstanceKlass::_array_klasses: Klass @ null
Array<Method*>* InstanceKlass::_methods: Array<Method*> @ 0x000000002cdbf3d8
Array<Method*>* InstanceKlass::_default_methods: Array<Method*> @ null
Array<Klass*>* InstanceKlass::_local_interfaces: Array<Klass*> @ 0x0000000025a20088
Array<Klass*>* InstanceKlass::_transitive_interfaces: Array<Klass*> @ 0x0000000025a20088
Array<u2>* InstanceKlass::_fields: Array<u2> @ 0x000000002cdbf3c0
u2 InstanceKlass::_java_fields_count: 0
ConstantPool* InstanceKlass::_constants: ConstantPool @ 0x000000002cdbf150
ClassLoaderData* InstanceKlass::_class_loader_data: ClassLoaderData @ 0x0000000027ef4f10
u2 InstanceKlass::_source_file_name_index: 37
char* InstanceKlass::_source_debug_extension: char @ null
Array<jushort>* InstanceKlass::_inner_classes: Array<jushort> @ 0x0000000025a20058
int InstanceKlass::_nonstatic_field_size: 0
int InstanceKlass::_static_field_size: 0
u2 InstanceKlass::_static_oop_field_count: 0
int InstanceKlass::_nonstatic_oop_map_size: 0
bool InstanceKlass::_is_marked_dependent: 0
u2 InstanceKlass::_minor_version: 0
u2 InstanceKlass::_major_version: 52
u1 InstanceKlass::_init_state: 4
Thread* InstanceKlass::_init_thread: Thread @ 0x00000000030b5000
int InstanceKlass::_vtable_len: 6
int InstanceKlass::_itable_len: 2
u1 InstanceKlass::_reference_type: 0
OopMapCache* InstanceKlass::_oop_map_cache: OopMapCache @ null
JNIid* InstanceKlass::_jni_ids: JNIid @ null
nmethod* InstanceKlass::_osr_nmethods_head: nmethod @ null
BreakpointInfo* InstanceKlass::_breakpoints: BreakpointInfo @ null
u2 InstanceKlass::_generic_signature_index: 0
jmethodID* InstanceKlass::_methods_jmethod_ids: jmethodID @ null
u2 InstanceKlass::_idnum_allocated_count: 2
Annotations* InstanceKlass::_annotations: Annotations @ 0x000000002cdbf5d8
nmethodBucket* InstanceKlass::_dependencies: nmethodBucket @ null
Array<int>* InstanceKlass::_method_ordering: Array<int> @ 0x0000000025a20040
Array<int>* InstanceKlass::_default_vtable_indices: Array<int> @ null
hsdb>
可以看到_annotations
的地址是0x000000002cdbf5d8
, 所以这个地址保存的值就是类注解的指针:
hsdb> mem 0x000000002cdbf5d8
0x000000002cdbf5d8: 0x000000002cdbf5c0
hsdb>
所以地址0x000000002cdbf5c0
就是Class的注解, 可以通过x64dbg
查看一下:
同时可以和010中的class文件对比一下:
这里的注解并不是以字符串的形式存在, 而是存储的是常量池的下标(常量池的下标是从0开始的), 表示的是常量池中第几个:
方法的注解
方法的注解是存在方法的属性之后的:
hsdb> printas Array<Method*> 0x000000002cdbf3d8
pointer to Array<Method*> @ 0x000000002cdbf3d8 (size = 16)
<opaque> Array<Method*>::_data: <opaque> @ 0x000000002cdbf3e0
hsdb> mem 0x000000002cdbf3d8 3
0x000000002cdbf3d8: 0x0000000000000002
0x000000002cdbf3e0: 0x000000002cdbf438
0x000000002cdbf3e8: 0x000000002cdbf550
hsdb>
0x000000002cdbf3d8
的值表示这个类有两个方法, 地址分别是0x000000002cdbf438
和0x000000002cdbf550
如果方法有注解的话, 方法地址加0x58
就是注解的值:
对比010: