Hotspot JVM内存形式

本文详细探讨了HotSpotVM如何在内存中表示Java类,特别是InstanceKlass结构,包括注解、常量池、版本号等信息。通过sa-jar工具分析了一个具体的例子,展示了如何找到类注解和方法注解在内存中的位置,并解释了这些信息如何与字节码文件中的内容对应。此外,还展示了如何通过内存地址查看和理解类的内部细节。
摘要由CSDN通过智能技术生成

简介

HotSpot VM是当前使用范围最广的Java虚拟机, 我们常规的Java开发通常都不会直接和底层内存打交道, 因为HotSpot VM已经帮我们把数据的解析,内存的分配(垃圾回收),类的加载等等事情都封装打包好了, 我们只需要处理我们关心的功能(编写并调用Java层的类, 反射等等), 不会关心底层的数据是如何传递和获取, 大大的方便了我们的日常开发, 甚至帮我们实现了非常好的跨平台能力, 在Windows上面写的Java程序在Linux和Mac上面同样可以稳定的运行, 那我们写的好的Java程序被编译成Class文件之后, 被加载的Class在底层内存中是如何存储的, 我们知道的注解, 常量池甚至方法在内存当中是什么样子的呢…

InstanceKlass

InstanceKlass是Java Class在JVM层面的内存表示, 包含了类在执行过程中需要的所有信息, 常用的如下:

字段名作用
_annotationsAnnotations类型的指针, 保存该使用的所有注解
_array_klasses数组元素为该类的数组Klass指针
_constantsConstantPool指针,该类的常量池
_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.TestInstanceKlass地址:

$ 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查看一下:
class_annotation
同时可以和010中的class文件对比一下:
010
这里的注解并不是以字符串的形式存在, 而是存储的是常量池的下标(常量池的下标是从0开始的), 表示的是常量池中第几个:
010

方法的注解

方法的注解是存在方法的属性之后的:

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的值表示这个类有两个方法, 地址分别是0x000000002cdbf4380x000000002cdbf550
如果方法有注解的话, 方法地址加0x58就是注解的值:
Method_Annotation
对比010:
010
010

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值