文章目录
前言
想要彻底学好JVM需要一定C语言基础,要理解头文件、指针、结构体、结构体指针、函数指针、类型别名等概念,本文尽量通俗易懂的介绍JNI,语雀地址:https://www.yuque.com/yangxiaofei-vquku/wmp1zm/dmz2gdOOP-Klass模型
在撸源码之前首先科普下JVM的OOP-Klass模型:
JVM内部基于oop-klass模型描述一个java类,将一个java类分为两个部分进行描述,其中第一个模型是oop,第二个模型是klass,其中oop用来表示堆中的java对象实例,储存着对象实例的非静态成员变量属性,不包含任何方法;Klass用来表示java类的元数据,包含了java类中声明的方法存在于方法区;oop有klass的引用,如此多个oop实例就不用都保存一份相同的方法信息了。
Klass分类
OOP分类
类加载在JVM源码分析
废话不多说先撸一发源码,java中的类加载到jvm中的过程依靠类加载器来完成,Classloader有三个核心方法loadClass()、findClass()、definclass(),简单介绍一下各种作用
loadClass()
- 查看这个Class是否已经别加载
- 如果没有被加载,继续往下走,查看父类加载器,递归调用loadClass()
- 如果父类加载器是null,说明是启动类加载器,查找对应的Class
- 如果都没有找到,就调用findClass(String)
findClass() - 根据名称或位置加载.class字节码,然后使用defineClass
definclass() - 把字节码转化为Class
由此可知Classloader加载类是最终起作用的是definclass方法,而definclass最终调用了Native方法definclass1()实现类的加载。
definclass1()的实现在jdk-jdk8-b120-源码/jdk/src/share/native/java/lang/ClassLoader.c中的Java_java_lang_ClassLoader_defineClass1()函数
/**
* 类加载native方法入口将字节码转化为Class
*/
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
jobject loader,
jstring name,
jbyteArray data,
jint offset,
jint length,
jobject pd,
jstring source)
{
jbyte *body;
char *utfName;
jclass result = 0;
char buf[128];
char* utfSource;
char sourceBuf[1024];
if (data == NULL) {
JNU_ThrowNullPointerException(env, 0);
return 0;
}
/* Work around 4153825. malloc crashes on Solaris when passed a
* negative size.
*/
if (length < 0) {
JNU_ThrowArrayIndexOutOfBoundsException(env, 0);
return 0;
}
body = (jbyte *)malloc(length);
if (body == 0) {
JNU_ThrowOutOfMemoryError(env, 0);
return 0;
}
(*env)->GetByteArrayRegion(env, data, offset, length, body);
if ((*env)->ExceptionOccurred(env))
goto free_body;
if (name != NULL) {
utfName = getUTF(env, name, buf, sizeof(buf));
if (utfName == NULL) {
JNU_ThrowOutOfMemoryError(env, NULL);
goto free_body;
}
VerifyFixClassname(utfName);
} else {
utfName = NULL;
}
if (source != NULL) {
utfSource = getUTF(env, source, sourceBuf, sizeof(sourceBuf));
if (utfSource == NULL) {
JNU_ThrowOutOfMemoryError(env, NULL);
goto free_utfName;
}
} else {
utfSource = NULL;
}
// 核心方法调用jvm.cpp中的静态函数JVM_DefineClassWithSource
result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);
if (utfSource && utfSource != sourceBuf)
free(utfSource);
free_utfName:
if (utfName && utfName != buf)
free(utfName);
free_body:
free(body);
return result;
}
调用了jdk-jdk8-b120-源码/hotspot/src/share/vm/prims/jvm.cpp中的JVM_DefineClassWithSource()函数
// JVM_DefineClassWithSource函数的定义
JVM_ENTRY(jclass, JVM_DefineClassWithSource(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source))
JVMWrapper2("JVM_DefineClassWithSource %s", name);
// 核心方法
return jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD);
JVM_END
调用了jdk-jdk8-b120-源码/hotspot/src/share/vm/prims/jvm.cpp中的jvm_define_class_common()函数
static jclass jvm_define_class_common(JNIEnv *env, const char *name,
jobject loader, const jbyte *buf,
jsize len, jobject pd, const char *source,
jboolean verify, TRAPS) {
if (source == NULL) source = "__JVM_DefineClass__";
assert(THREAD->is_Java_thread(), "must be a JavaThread");
JavaThread* jt = (JavaThread*) THREAD;
PerfClassTraceTime vmtimer(ClassLoader::perf_define_appclass_time(),
ClassLoader::perf_define_appclass_selftime(),
ClassLoader::perf_define_appclasses(),
jt->get_thread_stat()->perf_recursion_counts_addr(),
jt->get_thread_stat()->perf_timers_addr(),
PerfClassTraceTime::DEFINE_CLASS);
if (UsePerfData) {
ClassLoader::perf_app_classfile_bytes_read()->inc(len);
}
// Since exceptions can be thrown, class initialization can take place
// if name is NULL no check for class name in .class stream has to be made.
TempNewSymbol class_name = NULL;
if (name != NULL) {
const int str_len = (int)strlen(name);
if (str_len > Symbol::max_length()) {
// It's impossible to create this class; the name cannot fit
// into the constant pool.
THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name);
}
class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL);
}
ResourceMark rm(THREAD);
ClassFileStream st((u1*) buf, len, (char *)source);
Handle class_loader (THREAD, JNIHandles::resolve(loader));
if (UsePerfData) {
is_lock_held_by_thread(class_loader,
ClassLoader::sync_JVMDefineClassLockFreeCounter(),
THREAD);
}
Handle protection_domain (THREAD, JNIHandles::resolve(pd));
// 核心方法调用 SystemDictionary::resolve_from_stream
Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader,
protection_domain, &st,
verify != 0,
CHECK_NULL);
if (TraceClassResolution && k != NULL) {
trace_class_resolution(k);
}
// 为class实例创建本地引用,防止class对象无GCROOT引用被gc掉
return (jclass) JNIHandles::make_local(env, k->java_mirror());
}
调用JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/systemDictionary.cpp的resolve_from_stream函数获取Klass的指针,最后从Klass中获取java_mirror也就是堆里的class实例,java_mirror是Class类在堆中的oop实例。
Klass* SystemDictionary::resolve_from_stream(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
ClassFileStream* st,
bool verify,
TRAPS) {
//...省略不重要代码
instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name,
loader_data,
protection_domain,
//...省略不重要代码
return k();
}
调用jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/classFileParser.cpp中的parseClassFile
函数,重头戏来了,这个方法贼鸡儿长,总结下完成的事情是:加载父类Klass、加载当前Klass、为当前Klass设置属性以及初始化默认属性值。
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
ClassLoaderData* loader_data,
Handle protection_domain,
KlassHandle host_klass,
GrowableArray<Handle>* cp_patches,
TempNewSymbol& parsed_name,
bool verify,
TRAPS) {
//...省略父类Klass逻辑
// We can now create the basic Klass* for this klass
// 获取加载.class文件保存在方法区的元数据instanceKlass
_klass = InstanceKlass::allocate_instance_klass(loader_data,
vtable_size,
itable_size,
info.static_field_size,
total_oop_map_size2,
rt,
access_flags,
name,
super_klass(),
!host_klass.is_null(),
CHECK_(nullHandle));
instanceKlassHandle this_klass (THREAD, _klass);
//...省略instanceKlass设置静态方法个数、静态变量个数、java镜像(class对象)、版本号...等属性代码
// Allocate mirror and initialize static fields
// 为klass和class实例互相设置引用,并为静态变量设置默认值以及完成对带final static的属性赋恒定值
java_lang_Class::create_mirror(this_klass, protection_domain, CHECK_(nullHandle));
// ...省略不关键代码
return this_klass;
}
下面先看一下JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/javaClasses.cpp中的create_mirror函数,为klass和class实例互相设置引用并初始化class的静态字段(在每个Klass中都会有个java_mirror字段用来保存Klass对应的Class实例是个oop模型,Klass不直接暴露给java代码,给到java代码的是java_mirror即Class实例)
oop java_lang_Class::create_mirror(KlassHandle k, Handle protection_domain, TRAPS) {
assert(k->java_mirror() == NULL, "should only assign mirror once");
// Use this moment of initialization to cache modifier_flags also,
// to support Class.getModifiers(). Instance classes recalculate
// the cached flags after the class file is parsed, but before the
// class is put into the system dictionary.
int computed_modifiers = k->compute_modifier_flags(CHECK_0);
k->set_modifier_flags(computed_modifiers);
// Class_klass has to be loaded because it is used to allocate
// the mirror.
if (SystemDictionary::Class_klass_loaded()) {
// Allocate mirror (java.lang.Class instance)
// 获取Class实例oop.
Handle mirror = InstanceMirrorKlass::cast(SystemDictionary::Class_klass())->allocate_instance(k, CHECK_0);
// 获取Class类对应的Klass InstanceMirrorKlass
InstanceMirrorKlass* mk = InstanceMirrorKlass::cast(mirror->klass());
java_lang_Class::set_static_oop_field_count(mirror(), mk->compute_static_oop_field_count(mirror()));
// It might also have a component mirror. This mirror must already exist.
if (k->oop_is_array()) {
Handle comp_mirror;
if (k->oop_is_typeArray()) {
BasicType type = TypeArrayKlass::cast(k())->element_type();
comp_mirror = Universe::java_mirror(type);
} else {
assert(k->oop_is_objArray(), "Must be");
Klass* element_klass = ObjArrayKlass::cast(k())->element_klass();
assert(element_klass != NULL, "Must have an element klass");
comp_mirror = element_klass->java_mirror();
}
assert(comp_mirror.not_null(), "must have a mirror");
// Two-way link between the array klass and its component mirror:
ArrayKlass::cast(k())->set_component_mirror(comp_mirror());
set_array_klass(comp_mirror(), k());
} else {
assert(k->oop_is_instance(), "Must be");
// Allocate a simple java object for a lock.
// This needs to be a java object because during class initialization
// it can be held across a java call.
typeArrayOop r = oopFactory::new_typeArray(T_INT, 0, CHECK_NULL);
set_init_lock(mirror(), r);
// Set protection domain also
set_protection_domain(mirror(), protection_domain());
// Initialize static fields
// 为final static 属性赋恒定值
InstanceKlass::cast(k())->do_local_static_fields(&initialize_static_field, CHECK_NULL);
}
return mirror();
} else {
if (fixup_mirror_list() == NULL) {
GrowableArray<Klass*>* list =
new (ResourceObj::C_HEAP, mtClass) GrowableArray<Klass*>(40, true);
set_fixup_mirror_list(list);
}
fixup_mirror_list()->push(k());
return NULL;
}
}
接下来看下mirror的函数allocate_instance究竟干了什么,它在jdk-jdk8-b120-源码/hotspot/src/share/vm/oops/instanceMirrorKlass.cpp中(每个类都对应一个Klass,instanceMirrorKlass就是Class类对于的Klass,保存了Class类里的方法信息),由源码可见Class对象是存在于堆中的,是从堆中申请的内存地址,同时为静态属性赋默认值
instanceOop InstanceMirrorKlass::allocate_instance(KlassHandle k, TRAPS) {
// Query before forming handle.
int size = instance_size(k);
KlassHandle h_k(THREAD, this);
// 在堆中申请内存创建Class实例,这里也证明了class实例是在堆中的。同时为静态属性赋默认值
instanceOop i = (instanceOop) CollectedHeap::Class_obj_allocate(h_k, size, k, CHECK_NULL);
return i;
}
接下来看下jdk-jdk8-b120-源码/hotspot/src/share/vm/gc_interface/collectedHeap.cpp中的Class_obj_allocate函数,可以看到Klass和Class互相持有对方的引用,Klass持有Class的引用论证了Person.Class和person.getClass()方法原理;Class持有Klass的引用论证了反射获取方法的原理。
oop CollectedHeap::Class_obj_allocate(KlassHandle klass, int size, KlassHandle real_klass, TRAPS) {
debug_only(check_for_valid_allocation_state());
assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
assert(size >= 0, "int won't convert to size_t");
HeapWord* obj;
assert(ScavengeRootsInCode > 0, "must be");
obj = common_mem_allocate_init(real_klass, size, CHECK_NULL);
post_allocation_setup_common(klass, obj);
assert(Universe::is_bootstrapping() ||
!((oop)obj)->is_array(), "must not be an array");
NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
oop mirror = (oop)obj;
java_lang_Class::set_oop_size(mirror, size);
// Setup indirections
if (!real_klass.is_null()) {
//为Class实例设置Klass引用
java_lang_Class::set_klass(mirror, real_klass());
// 为Klass设置java_mirror属性指向堆里的Class实例
real_klass->set_java_mirror(mirror);
}
InstanceMirrorKlass* mk = InstanceMirrorKlass::cast(mirror->klass());
assert(size == mk->instance_size(real_klass), "should have been set");
// notify jvmti and dtrace
post_allocation_notify(klass, (oop)obj);
return mirror;
}
Class在Klass中的引用就是java_mirror在HSDB中可以清楚看到,但是Klass在Class中的引用在HSDB中是看不到的,也就是在Class中没有Klass相关的属性,那又是怎么储存的呢?jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/javaClasses.cpp中的set_klass()函数
void java_lang_Class::set_klass(oop java_class, Klass* klass) {
assert(java_lang_Class::is_instance(java_class), "must be a Class object");
// 在数据区基于偏移量设置klass引用,_klass_offset为相对于数据区的固定偏移量
java_class->metadata_field_put(_klass_offset, klass);
}
C++并不像Java一样,保存信息时非要在类中定义出相关属性,C++只是在分配内存时为要存储的信息分配好特定的内存,然后直接通过内存偏移来操作即可,所以C++是允许无属性存储引用的,只要记录相对偏移量就可以进行存取。
下面再回到JVM/jdk-jdk8-b120-源码/hotspot/src/share/vm/classfile/javaClasses.cpp中的create_mirror函数中的第二个关键方法do_local_static_fields函数,在创建Class实例后需要为final属性赋恒定值。这个函数的第一个参数是initialize_static_field的函数引用,会被回调执行,核心代码也在initialize_static_field函数中。
static void initialize_static_field(fieldDescriptor* fd, TRAPS) {
Handle mirror (THREAD, fd->field_holder()->java_mirror());
assert(mirror.not_null() && fd->is_static(), "just checking");
// 是否带final static
if (fd->has_initial_value()) {
BasicType t = fd->field_type();
switch (t) {
case T_BYTE:
mirror()->byte_field_put(fd->offset(), fd->int_initial_value());
break;
case T_BOOLEAN:
mirror()->bool_field_put(fd->offset(), fd->int_initial_value());
break;
case T_CHAR:
mirror()->char_field_put(fd->offset(), fd->int_initial_value());
break;
case T_SHORT:
mirror()->short_field_put(fd->offset(), fd->int_initial_value());
break;
case T_INT:
mirror()->int_field_put(fd->offset(), fd->int_initial_value());
break;
case T_FLOAT:
mirror()->float_field_put(fd->offset(), fd->float_initial_value());
break;
case T_DOUBLE:
mirror()->double_field_put(fd->offset(), fd->double_initial_value());
break;
case T_LONG:
mirror()->long_field_put(fd->offset(), fd->long_initial_value());
break;
case T_OBJECT:
{
#ifdef ASSERT
TempNewSymbol sym = SymbolTable::new_symbol("Ljava/lang/String;", CHECK);
assert(fd->signature() == sym, "just checking");
#endif
oop string = fd->string_initial_value(CHECK);
mirror()->obj_field_put(fd->offset(), string);
}
break;
default:
THROW_MSG(vmSymbols::java_lang_ClassFormatError(),
"Illegal ConstantValue attribute in class file");
}
}
}
至此类加载的源码分析完毕,总结下在源码分析中我们的收获:
- .class文件被jvm加载后会在方法区生产一个instanceKlass用来储存Java类的方法信息、静态方法个数、静态变量个数、版本号、常量池等信息;
- 会在堆区产生一个instanceOOP代表Java类对应的java.lang.Class实例,class实例里保存了Java类的静态变量,在创建class实例时会为静态变量设置默认值,之后调用initialize_static_field函数为带final修饰的静态常量赋恒定值(可以在常量池找到恒定值符号引用的常量);
- 在Klass和Class互相持有引用,在instanceKlass中有个java_mirror的属性用于储存class的oop引用,在class里基于固定的指针偏移量_klass_offset来储存klass引用;
- instanceMirrorKlass用来表时java.lang.Class类的Klass,同时java.lang.Class类在堆中也会有对应oop类型的class实例(Class.class),那这个oop中即保存了Class对象的非静态字段值,又包含了Class类自身也定义的静态字段,故比较特殊。
用HSDB分析JVM的运行时数据
用HSDB分析Person类的运行时数据
public class Person {
// 静态常量(常量池中不存在恒定值引用)
private final static String finalStr1= UUID.randomUUID().toString();
// 静态常量(常量池中存在恒定值引用)
private final static String finalStr2="final String";
// String类型静态变量
private static String staticStr="static String";
// int类型静态变量
private static int staticInt=1;
// String成员变量
private String a="String";
// int类型成员变量
private int b=2;
/******************get/set方法****************************/
public static String getFinalStr1() {
return finalStr1;
}
public static String getFinalStr2() {
return finalStr2;
}
public static String getStaticStr() {
return staticStr;
}
public static void setStaticStr(String staticStr) {
Person.staticStr = staticStr;
}
public static int getStaticInt() {
return staticInt;
}
public static void setStaticInt(int staticInt) {
Person.staticInt = staticInt;
}
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
}
/**
* JVM内存与垃圾回收统篇之类加载子系统
*/
public class Demo1 {
public static void main(String[] args) {
Person person1=new Person();
while (true);
}
}
Person在堆里的oop实例person1
Person类在方法区的Klass
在堆中Person.class oop实例
由上图可以得到Person类中的静态变量都保存在Klass中的java_mirror属性中,也就是都保存在Person.class实例中,成员变量保存在person1对应的Person oop实例中。
最后总结一波Person在JVM中的堆、栈、方法区的Klass-OOP引用关系:
(上图~)
final static 初始化问题
结合上面的java代码再从字节码层面论证一下"类加载在JVM源码分析"中的第二个总结:
会在堆区产生一个instanceOOP代表Java类对应的java.lang.Class实例,class实例里保存了Java类的静态变量,在创建class实例时会为静态变量设置默认值,之后调用initialize_static_field函数为带final修饰的静态常量赋恒定值(可以在常量池找到恒定值符号引用的常量);
静态变量存放在堆中的class实例里,这个上面三张图可以证明这一点,这次证明的主要是后半句话,final的静态常量赋值问题。
Person的
Person的常量池
Constant pool:
#1 = Methodref #13.#47 // java/lang/Object."<init>":()V
#2 = String #48 // String
#3 = Fieldref #6.#49 // jvm/memory/loading/Person.a:Ljava/lang/String;
#4 = Fieldref #6.#50 // jvm/memory/loading/Person.b:I
#5 = Fieldref #6.#51 // jvm/memory/loading/Person.finalStr1:Ljava/lang/String;
#6 = Class #52 // jvm/memory/loading/Person
#7 = String #53 // final String
#8 = Fieldref #6.#54 // jvm/memory/loading/Person.staticStr:Ljava/lang/String;
#9 = Fieldref #6.#55 // jvm/memory/loading/Person.staticInt:I
#10 = Methodref #56.#57 // java/util/UUID.randomUUID:()Ljava/util/UUID;
#11 = Methodref #56.#58 // java/util/UUID.toString:()Ljava/lang/String;
#12 = String #59 // static String
#13 = Class #60 // java/lang/Object
#14 = Utf8 finalStr1
#15 = Utf8 Ljava/lang/String;
#16 = Utf8 finalStr2
#17 = Utf8 ConstantValue
#18 = Utf8 staticStr
#19 = Utf8 staticInt
#20 = Utf8 I
#21 = Utf8 a
#22 = Utf8 b
#23 = Utf8 <init>
#24 = Utf8 ()V
#25 = Utf8 Code
#26 = Utf8 LineNumberTable
#27 = Utf8 LocalVariableTable
#28 = Utf8 this
#29 = Utf8 Ljvm/memory/loading/Person;
#30 = Utf8 getFinalStr1
#31 = Utf8 ()Ljava/lang/String;
#32 = Utf8 getFinalStr2
#33 = Utf8 getStaticStr
#34 = Utf8 setStaticStr
#35 = Utf8 (Ljava/lang/String;)V
#36 = Utf8 getStaticInt
#37 = Utf8 ()I
#38 = Utf8 setStaticInt
#39 = Utf8 (I)V
#40 = Utf8 getA
#41 = Utf8 setA
#42 = Utf8 getB
#43 = Utf8 setB
#44 = Utf8 <clinit>
#45 = Utf8 SourceFile
#46 = Utf8 Person.java
#47 = NameAndType #23:#24 // "<init>":()V
#48 = Utf8 String
#49 = NameAndType #21:#15 // a:Ljava/lang/String;
#50 = NameAndType #22:#20 // b:I
#51 = NameAndType #14:#15 // finalStr1:Ljava/lang/String;
#52 = Utf8 jvm/memory/loading/Person
#53 = Utf8 final String
#54 = NameAndType #18:#15 // staticStr:Ljava/lang/String;
#55 = NameAndType #19:#20 // staticInt:I
#56 = Class #61 // java/util/UUID
#57 = NameAndType #62:#63 // randomUUID:()Ljava/util/UUID;
#58 = NameAndType #64:#31 // toString:()Ljava/lang/String;
#59 = Utf8 static String
#60 = Utf8 java/lang/Object
#61 = Utf8 java/util/UUID
#62 = Utf8 randomUUID
#63 = Utf8 ()Ljava/util/UUID;
#64 = Utf8 toString
在Person中一共有四个静态变量,finalStr1,finalStr2,staticStr,staticInt其中前两个被final修饰。
在Person的类初始化方法的字节码中发现没有finalStr2相关的初始化指令,原因是在执行之前finalStr2已经进行了赋值操作,因为finalStr2是显式赋值且赋值过程中不涉及方法调用,其显式赋的值"final String"在编译时期就已经确认,被存放在常量池#53的位置,然后在创建class实例后通过initialize_static_field函数进行赋值。
总结一波一个类的各种成员变量的初始化顺序:
- 被static修饰的全部变量赋默认值,在创建Class实例时;
- 被final static修饰的静态常量**(可以在常量池找到恒定值符号引用的常量)**在创建Class实例后,调用initialize_static_field进行赋值;
- 在Person类初始化时调用方法进行赋值
- 在创建类Person类实例时调用构造方法对非静态成员变量进行赋值。
参考:https://blog.51cto.com/u_10912795/2775854 类加载阶段之准备阶段
博主语雀地址:https://www.yuque.com/yangxiaofei-vquku/wmp1zm/mel3x8