JVM系列 —— 类的生命周期之类加载

这是 JVM 系列的第三篇,见 JVM 系列

写在前面

在提到 JVM 类加载的时候,经常容易把类加载机制和类加载器搞混淆,虽然两者紧密联系,但是实际上应该从不同的角度去理解,类加载机制可以看做是一个流程,类似软件产品从需求(class 文件格式)到落地(class 对象)的过程,而类加载器在类加载机制的流程中充当执行者,类似于软件产品中输出的IT人员。所这里想分开讨论一下。

首先来看类加载机制

类的生命周期说法大致相同,从大的角度来说,包括:加载 –> 链接 -> 初始化 -> 使用 -> 卸载
从小的细节来说,包括:加载 –> 验证 –> 准备 –> 解析 -> 初始化 -> 使用 -> 卸载
画个图如下:

类的生命周期

  • 加载:就是通过类的二进制表示来创建类或接口的过程,大白话就是,找到 class 文件,放到内存里面(在 metaspace 区域),构建java类的原型–类模板对象,相当于一个快照,jvm 随便用,给机器看的。

  • 链接:将类或接口并入虚拟机运行时状态的过程。链接的主要作用就是将字节码指令中的常量池的符号引用解析为指针、偏移量等内存地址的直接引用。

    这里有点晕,需要看一下计算机操作系统的链接部分内容。

  • 初始化:类或接口的初始化是执行类或接口的初始化方法 <clinit>,如果类中有 static字段或static{}代码块,会产生一个 <clinit>的方法。

    与实例的初始化方法 <init> 不同,实例的初始化是调用构造器,发生在类或接口已经初始化之后,而 <clinit> 发生在类加载到虚拟机的初始化阶段。

其次来看类加载器

JVM 规范支持两种类加载器:

  • java 虚拟机提供的引导类加载器(bootstrap class loader)
  • 用户自定义的类加载器

而针对 JVM 实现上,java 体系中有三种类加载器,JDK 8 实现的类加载的三级层次结构:

  • Boostrap class loader
    主要加载 rt.jar 和 其他在 $JAVA_HOME/jre/lib 路径下面的核心类库,还有通过参数 -Xbootclasspath 指定的目录。
  • Extension class loader
    主要加载 $JAVA_HOME/jre/lib/ext 下面的类还有系统属性 java.ext.dirs 指定目录的类库。
  • System class loader
    也成为 Application class loader,主要加载应用程序的类库,即有系统环境变量 ClassPath-cp 命令或者系统属性 java.class.path 指定的类库。

    JDK 9 起,jdk 的类库管理发生了很大的变化,引入了平台模块系统(参考 Java Platform Module System (JSR 376)),把各个类库拆分组合成一个一个的模块,此时,针对类加载器的变化就是, Extension Classloader 改名成 Platform class loader,并且和 Bootstrap class loader 分别加载不同的模块,具体的模块参考下面的 JEP 文档。
    参考:

当然,除了上述的三个层级的类加载器,也可以自定义类加载器。下面的示例展示了 jdk8 和 jdk11 的不同类加载器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ClassLoaderLevel {

/**
* JDK 8 :
String 类的 ClassLoader 是:null
--------------
示例类的 ClassLoader 是:sun.misc.Launcher$AppClassLoader@18b4aac2
示例类的 ClassLoader 的父加载器是:sun.misc.Launcher$ExtClassLoader@2f0e140b
示例类的 ClassLoader 的 ClassLoader 是:null
--------------
系统 ClassLoader 是:sun.misc.Launcher$AppClassLoader@18b4aac2
系统 ClassLoader 的父加载器是:sun.misc.Launcher$ExtClassLoader@2f0e140b
系统 ClassLoader 的 ClassLoader 是:null
*/

/**
* JDK 11 打印:
String 类的 ClassLoader 是:null
--------------
示例类的 ClassLoader 是:jdk.internal.loader.ClassLoaders$AppClassLoader@1f89ab83
示例类的 ClassLoader 的父加载器是:jdk.internal.loader.ClassLoaders$PlatformClassLoader@6108b2d7
示例类的 ClassLoader 的 ClassLoader 是:null
--------------
系统 ClassLoader 是:jdk.internal.loader.ClassLoaders$AppClassLoader@1f89ab83
系统 ClassLoader 的父加载器是:jdk.internal.loader.ClassLoaders$PlatformClassLoader@6108b2d7
系统 ClassLoader 的 ClassLoader 是:null
*/

public static void main(String[] args) {
System.out.println("String 类的 ClassLoader 是:" + String.class.getClassLoader());
System.out.println("--------------");
System.out.println("示例类的 ClassLoader 是:" + ClassLoaderLevel.class.getClassLoader());
System.out.println("示例类的 ClassLoader 的父加载器是:" + ClassLoaderLevel.class.getClassLoader().getParent());
System.out.println("示例类的 ClassLoader 的 ClassLoader 是:" + ClassLoaderLevel.class.getClassLoader().getClass().getClassLoader());
System.out.println("--------------");
System.out.println("系统 ClassLoader 是:" + ClassLoader.getSystemClassLoader());
System.out.println("系统 ClassLoader 的父加载器是:" + ClassLoader.getSystemClassLoader().getParent());
System.out.println("系统 ClassLoader 的 ClassLoader 是:" + ClassLoader.getSystemClassLoader().getClass().getClassLoader());

System.out.println(System.getProperty("sun.boot.class.path")); //jdk8 打印 rt.jar 的类,jdk11 空
System.out.println(System.getProperty("java.ext.dirs")); // jdk8 打印 ext 类, jdk11 空
System.out.println(System.getProperty("java.class.path")); // jdk8 和 jdk 11 都打印 classpath 下的 jar
}

}

虚拟机启动

JVM 虚拟机规范在第五章说明了虚拟机启动时的步骤,大概流程可以用如下图所示:
虚拟机启动示意图

oop-klass 模型

在说到类加载的时候,为了更好的弄清楚类加载的过程和原理,必然要提到 oop-klass 模型,简单的说,每一个 java class 加载到 JVM 中,JVM会产生一个与之对应的类模板对象,实际上是一个 C++ 的实例。这个类模板对象,从两个维度来说,一个是 oop(Ordinary Object Pointer) 模型,一个是 klass 模型,klass 存放的是这个类的元数据,类的信息,而 oop 则保存了实例的数据。举个例子:

1
2
A a = new A();
A b = new A();

上面的代码最终反映到 JVM 层面,a 和 b 的 JVM klass 都是相同的,但是 oop 不同。看一下 jdk8 源码 的 oop 体系和 klass 体系:

  • oop

    Hotspot 里面的 oop 其实就是 GC 托管的指针,每一个 oop 都是一种 xxxOopDesc* 类型的指针。在 Hotspot 里面 oop 就指一个 真的指针。

    oop

    看 JVM 源码 hotspot/src/share/vm/oops/oop.hpp{name}Desc 描述了 java 对象的结构, 能够从 C++ 访问得到。
    oop 源码

    hotspot/src/share/vm/oops/oopsHierarchy.hpp 定义了 oop 的层级和含义:
    oop 层级类

    在 JVM 中, instanceOop 表示一个 java 的一个实例对象,这个实例对象是可以从 C++ 层面能访问到的。markOop 是一个比较特殊的类型,看起来是一个像指针,实际上是藏在指针里面的对象(数据)。不收 GC 托管。只是为了满足“面向对象”编程而设计的。路径 share/vm/oops/markOop.hpp
    markOop

    oop 一般有对象头、对象专有属性和数据体这 3 部分组成。

  • klass

    按照 官方文档,klass 提供两种能力:

    • 提供一个与 java 类对等的 C++ 类型描述
    • 提供虚拟机内部的函数分发机制(没有搞过 C++,不是很理解)
      JVM 里面 klass 的层级结构如下:klass

总之一句话,每一个 java class 字节流文件,最终都会 JVM 内部产生一个 klassOop 和它对等,java类的字段、方法以及常量池都会保存到 klassOop 实例对象中。

类加载

虚拟机运行的时候,一个类或接口不仅仅是有二进制名称来确定的,还有它的定义类加载共同确定的,也就是说,类名称相同,但是是不同类加载加载的,那么最后生成实例是两个不同类型的实例。

在上一篇讲过 class 文件格式,那么 JVM 是怎样去解析这个 class 文件呢?

这里要说明的是,这个 class 文件格式并不仅仅局限于二进制文件,也有可能从网络加载,但是最终解析的都是 class 文件的二进制字节流

对于类的解析逻辑在源码 hotspot/src/share/vm/classfile/classFileParser.cppClassFileParser::parseClassFile() 函数中,贴一下源码:

类文件解析源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
ClassLoaderData* loader_data,
Handle protection_domain,
KlassHandle host_klass,
GrowableArray<Handle>* cp_patches,
TempNewSymbol& parsed_name,
bool verify,
TRAPS) {
// class 流
ClassFileStream* cfs = stream();

... ...

// 魔数值校验
u4 magic = cfs->get_u4_fast();
guarantee_property(magic == JAVA_CLASSFILE_MAGIC,
"Incompatible magic value %u in class file %s",
magic, CHECK_(nullHandle));

// 版本号校验
u2 minor_version = cfs->get_u2_fast();
u2 major_version = cfs->get_u2_fast();

if (DumpSharedSpaces && major_version < JAVA_1_5_VERSION) {
... ...
}

// Check version numbers - we check this even with verifier off
if (!is_supported_version(major_version, minor_version)) {
... ...
}

_major_version = major_version;
_minor_version = minor_version;


// 解析常量池
constantPoolHandle cp = parse_constant_pool(CHECK_(nullHandle));

// 访问标志
AccessFlags access_flags;

... ...

if ((flags & JVM_ACC_INTERFACE) && _major_version < JAVA_6_VERSION) {
... ...
}


// 类索引
_this_class_index = cfs->get_u2_fast();

... ...

// 父类索引
instanceKlassHandle super_klass = parse_super_class(super_class_index,
CHECK_NULL);

// 接口
u2 itfs_len = cfs->get_u2_fast();
Array<Klass*>* local_interfaces =
parse_interfaces(itfs_len, protection_domain, _class_name,
&has_default_methods, CHECK_(nullHandle));

u2 java_fields_count = 0;
// 字段解析
Array<u2>* fields = parse_fields(class_name,
access_flags.is_interface(),
&fac, &java_fields_count,
CHECK_(nullHandle));
// 方法解析
Array<Method*>* methods = parse_methods(access_flags.is_interface(),
&promoted_flags,
&has_final_method,
&declares_default_methods,
CHECK_(nullHandle));

// 属性解析
ClassAnnotationCollector parsed_annotations;
parse_classfile_attributes(&parsed_annotations, CHECK_(nullHandle));

//创建与 java 类对等的内部对象
_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);


// Fill in information already parsed
this_klass->set_should_verify_class(verify);
jint lh = Klass::instance_layout_helper(info.instance_size, false);
this_klass->set_layout_helper(lh);

if (has_final_method) {
this_klass->set_has_final_method();
}
this_klass->copy_method_ordering(method_ordering, CHECK_NULL);
// The InstanceKlass::_methods_jmethod_ids cache
// is managed on the assumption that the initial cache
// size is equal to the number of methods in the class. If
// that changes, then InstanceKlass::idnum_can_increment()
// has to be changed accordingly.
this_klass->set_initial_method_idnum(methods->length());
this_klass->set_name(cp->klass_name_at(_this_class_index));
if (is_anonymous()) // I am well known to myself
cp->klass_at_put(_this_class_index, this_klass()); // eagerly resolve

this_klass->set_minor_version(minor_version);
this_klass->set_major_version(major_version);
this_klass->set_has_default_methods(has_default_methods);
this_klass->set_declares_default_methods(declares_default_methods);

if (!host_klass.is_null()) {
assert (this_klass->is_anonymous(), "should be the same");
this_klass->set_host_klass(host_klass());
}

if (cached_class_file != NULL) {
// JVMTI: we have an InstanceKlass now, tell it about the cached bytes
this_klass->set_cached_class_file(cached_class_file);
}

// 创建镜像类和静态字段初始化
java_lang_Class::create_mirror(this_klass, class_loader, protection_domain,
CHECK_(nullHandle));

}
}

从源码里面可以看到,调用klass 的子类 InstanceClass 调用方法 allocate_instance_klass() 构建了一个 instanceKlass 对象实例。InstanceKlass 是虚拟机级别的java类的对等对象,包含了在运行期间构建一个类所有必要的信息。至此,java 类的生命周期的第一个阶段 —— 加载 就结束了。下面的源码是 InstanceKlass 的结构:

hotspot/src/share/vm/oops/instanceKlass.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
class InstanceKlass: public Klass {
friend class VMStructs;
friend class ClassFileParser;
friend class CompileReplay;

protected:
// Constructor
InstanceKlass(int vtable_len,
int itable_len,
int static_field_size,
int nonstatic_oop_map_size,
ReferenceType rt,
AccessFlags access_flags,
bool is_anonymous);
public:
static InstanceKlass* allocate_instance_klass(
ClassLoaderData* loader_data,
int vtable_len,
int itable_len,
int static_field_size,
int nonstatic_oop_map_size,
ReferenceType rt,
AccessFlags access_flags,
Symbol* name,
Klass* super_klass,
bool is_anonymous,
TRAPS);

InstanceKlass() { assert(DumpSharedSpaces || UseSharedSpaces, "only for CDS"); }

// See "The Java Virtual Machine Specification" section 2.16.2-5 for a detailed description
// of the class loading & initialization procedure, and the use of the states.
enum ClassState {
allocated, // allocated (but not yet linked)
loaded, // loaded and inserted in class hierarchy (but not linked yet)
linked, // successfully linked/verified (but not initialized yet)
being_initialized, // currently running class initializer
fully_initialized, // initialized (successfull final state)
initialization_error // error happened during initialization
};

static int number_of_instance_classes() { return _total_instanceKlass_count; }

private:
static volatile int _total_instanceKlass_count;

protected:
// Annotations for this class
Annotations* _annotations;
// Array classes holding elements of this class.
Klass* _array_klasses;
// Constant pool for this class.
ConstantPool* _constants;
// The InnerClasses attribute and EnclosingMethod attribute. The
// _inner_classes is an array of shorts. If the class has InnerClasses
// attribute, then the _inner_classes array begins with 4-tuples of shorts
// [inner_class_info_index, outer_class_info_index,
// inner_name_index, inner_class_access_flags] for the InnerClasses
// attribute. If the EnclosingMethod attribute exists, it occupies the
// last two shorts [class_index, method_index] of the array. If only
// the InnerClasses attribute exists, the _inner_classes array length is
// number_of_inner_classes * 4. If the class has both InnerClasses
// and EnclosingMethod attributes the _inner_classes array length is
// number_of_inner_classes * 4 + enclosing_method_attribute_size.
Array<jushort>* _inner_classes;

// the source debug extension for this klass, NULL if not specified.
// Specified as UTF-8 string without terminating zero byte in the classfile,
// it is stored in the instanceklass as a NULL-terminated UTF-8 string
char* _source_debug_extension;
// Array name derived from this class which needs unreferencing
// if this class is unloaded.
Symbol* _array_name;

// Number of heapOopSize words used by non-static fields in this klass
// (including inherited fields but after header_size()).
int _nonstatic_field_size;
int _static_field_size; // number words used by static fields (oop and non-oop) in this klass
// Constant pool index to the utf8 entry of the Generic signature,
// or 0 if none.
u2 _generic_signature_index;
// Constant pool index to the utf8 entry for the name of source file
// containing this klass, 0 if not specified.
u2 _source_file_name_index;
u2 _static_oop_field_count;// number of static oop fields in this klass
u2 _java_fields_count; // The number of declared Java fields
int _nonstatic_oop_map_size;// size in words of nonstatic oop map blocks

// _is_marked_dependent can be set concurrently, thus cannot be part of the
// _misc_flags.
bool _is_marked_dependent; // used for marking during flushing and deoptimization
bool _is_being_redefined; // used for locking redefinition
bool _has_unloaded_dependent;

enum {
_misc_rewritten = 1 << 0, // methods rewritten.
_misc_has_nonstatic_fields = 1 << 1, // for sizing with UseCompressedOops
_misc_should_verify_class = 1 << 2, // allow caching of preverification
_misc_is_anonymous = 1 << 3, // has embedded _host_klass field
_misc_is_contended = 1 << 4, // marked with contended annotation
_misc_has_default_methods = 1 << 5, // class/superclass/implemented interfaces has default methods
_misc_declares_default_methods = 1 << 6, // directly declares default methods (any access)
_misc_has_been_redefined = 1 << 7 // class has been redefined
};
u2 _misc_flags;
u2 _minor_version; // minor version number of class file
u2 _major_version; // major version number of class file
Thread* _init_thread; // Pointer to current thread doing initialization (to handle recursive initialization)
int _vtable_len; // length of Java vtable (in words)
int _itable_len; // length of Java itable (in words)
OopMapCache* volatile _oop_map_cache; // OopMapCache for all methods in the klass (allocated lazily)
MemberNameTable* _member_names; // Member names
JNIid* _jni_ids; // First JNI identifier for static fields in this class
jmethodID* _methods_jmethod_ids; // jmethodIDs corresponding to method_idnum, or NULL if none
nmethodBucket* _dependencies; // list of dependent nmethods
nmethod* _osr_nmethods_head; // Head of list of on-stack replacement nmethods for this class
BreakpointInfo* _breakpoints; // bpt lists, managed by Method*
// Linked instanceKlasses of previous versions
InstanceKlass* _previous_versions;
// JVMTI fields can be moved to their own structure - see 6315920
// JVMTI: cached class file, before retransformable agent modified it in CFLH
JvmtiCachedClassFileData* _cached_class_file;

volatile u2 _idnum_allocated_count; // JNI/JVMTI: increments with the addition of methods, old ids don't change

// Class states are defined as ClassState (see above).
// Place the _init_state here to utilize the unused 2-byte after
// _idnum_allocated_count.
u1 _init_state; // state of class
u1 _reference_type; // reference type

JvmtiCachedClassFieldMap* _jvmti_cached_class_field_map; // JVMTI: used during heap iteration

NOT_PRODUCT(int _verify_count;) // to avoid redundant verifies

// Method array.
Array<Method*>* _methods;
// Default Method Array, concrete methods inherited from interfaces
Array<Method*>* _default_methods;
// Interface (Klass*s) this class declares locally to implement.
Array<Klass*>* _local_interfaces;
// Interface (Klass*s) this class implements transitively.
Array<Klass*>* _transitive_interfaces;
// Int array containing the original order of method in the class file (for JVMTI).
Array<int>* _method_ordering;
// Int array containing the vtable_indices for default_methods
// offset matches _default_methods offset
Array<int>* _default_vtable_indices;

// Instance and static variable information, starts with 6-tuples of shorts
// [access, name index, sig index, initval index, low_offset, high_offset]
// for all fields, followed by the generic signature data at the end of
// the array. Only fields with generic signature attributes have the generic
// signature data set in the array. The fields array looks like following:
//
// f1: [access, name index, sig index, initial value index, low_offset, high_offset]
// f2: [access, name index, sig index, initial value index, low_offset, high_offset]
// ...
// fn: [access, name index, sig index, initial value index, low_offset, high_offset]
// [generic signature index]
// [generic signature index]
// ...
Array<u2>* _fields;

/** 预留空间来存放 vtable(虚拟表), itable(接口表), 静态字段,引用的 map
*/
// embedded Java vtable follows here
// embedded Java itables follows here
// embedded static fields follows here
// embedded nonstatic oop-map blocks follows here


// embedded implementor of this interface follows here
// The embedded implementor only exists if the current klass is an
// iterface. The possible values of the implementor fall into following
// three cases:
// NULL: no implementor.
// A Klass* that's not itself: one implementor.
// Itself: more than one implementors.
// embedded host klass follows here
// The embedded host klass only exists in an anonymous class for
// dynamic language support (JSR 292 enabled). The host class grants
// its access privileges to this class also. The host class is either
// named, or a previously loaded anonymous class. A non-anonymous class
// or an anonymous class loaded through normal classloading does not
// have this embedded field.
//

... ...
}

在上面解析 class 文件的源码中,我们看到了在调用 `allocate_instance_klass()` 创建一个 instanceKlass 之后,看到 `create_mirror()` 的函数,创建一个镜像类,接下来看看这个镜像类。

镜像类和静态字段

看一下源码,位于 hotspot/src/share/vm/classfile/javaClasses.cpp

java_lang_Class::create_mirror() 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void java_lang_Class::create_mirror(KlassHandle k, Handle class_loader,
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);
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)
Handle mirror = InstanceMirrorKlass::cast(SystemDictionary::Class_klass())->allocate_instance(k, CHECK);

// Setup indirection from mirror->klass
if (!k.is_null()) {
java_lang_Class::set_klass(mirror(), k());
}

InstanceMirrorKlass* mk = InstanceMirrorKlass::cast(mirror->klass());
assert(oop_size(mirror()) == mk->instance_size(k), "should have been set");

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");

initialize_mirror_fields(k, mirror, protection_domain, THREAD);
if (HAS_PENDING_EXCEPTION) {
// If any of the fields throws an exception like OOM remove the klass field
// from the mirror so GC doesn't follow it after the klass has been deallocated.
// This mirror looks like a primitive type, which logically it is because it
// it represents no class.
java_lang_Class::set_klass(mirror(), NULL);
return;
}
}

// set the classLoader field in the java_lang_Class instance
assert(class_loader() == k->class_loader(), "should be same");
set_class_loader(mirror(), class_loader());

// Setup indirection from klass->mirror last
// after any exceptions can happen during allocations.
if (!k.is_null()) {
k->set_java_mirror(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());
}
}


从代码的注释里面可以看到,mirror 镜像类也是一个 instanceKlass 的实例,`SystemDictionary::Class_klass()` 返回的就是 `java_lang_Class` 类型,然后 `allocate_instance()` 这个方法就是创建, `java.lang.Class` 这个 java 类型在 JVM 内部的对等体。再调用方法 `set_klass()`,让当前创建的 klassOop 引用刚刚实例化的 `java_lang_Class` 对象。**这么做的目的是为了被 Java 程序调用,而 instanceKlass 是 JVM 内部使用的。也就是说 JVM 暴露给 java 程序的是 mirror 实例,而不是 instanceKlass,这与前面的理论一致。** JDK 库中的反射等工具类,其实都是基于 `java_lang_Class` 这个内部镜像类实现的。

另外在 allocate_instance_klass() 中,我们并没有看到静态字段的初始化,实际上静态字段的初始化是放到了镜像类中,创建镜像类的方法中有个方法 initialize_mirror_fields(),同样位于 hotspot/src/share/vm/classfile/javaClasses.cpp,贴下源码实现:

initialize_mirror_fields() 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void java_lang_Class::initialize_mirror_fields(KlassHandle k,
Handle mirror,
Handle protection_domain,
TRAPS) {
// 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);
set_init_lock(mirror(), r);

// Set protection domain also
set_protection_domain(mirror(), protection_domain());

// Initialize static fields
InstanceKlass::cast(k())->do_local_static_fields(&initialize_static_field, mirror, CHECK);
}

从代码中找到方法 do_local_static_fields(),这个方法就是初始化静态字段的方法,位于 hotspot/src/share/vm/oops/instanceKlass.cpp 中,如下:

do_local_static_fields() 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

void InstanceKlass::do_local_static_fields(void f(fieldDescriptor*, Handle, TRAPS), Handle mirror, TRAPS) {
instanceKlassHandle h_this(THREAD, this);
do_local_static_fields_impl(h_this, f, mirror, CHECK);
}


void InstanceKlass::do_local_static_fields_impl(instanceKlassHandle this_k,
void f(fieldDescriptor* fd, Handle mirror, TRAPS), Handle mirror, TRAPS) {
for (JavaFieldStream fs(this_k()); !fs.done(); fs.next()) {
if (fs.access_flags().is_static()) {
fieldDescriptor& fd = fs.field_descriptor();
f(&fd, mirror, CHECK);
}
}
}

javaClasses.cpp 中调用 instanceKlass 的 do_local_static_fields() 方法时,传入了一个函数指针,这个函数指针代表如下,位于 hotspot/src/share/vm/classfile/javaClasses.cpp

给静态字段赋初值的函数指针源码 &initialize_static_field
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static void initialize_static_field(fieldDescriptor* fd, Handle mirror, TRAPS) {
assert(mirror.not_null() && fd->is_static(), "just checking");
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");
}
}
}

在上面的函数中,通过 mirror() -> xxx_field_put() 的方法将不同类型的静态字段存储到镜像类中。实际上,静态字段在类加载的阶段就已经完成了初始化。这里会有疑问:为什么要放在镜像类中? 因为前面说过镜像类是给 java 的应用程序调用,所以 JDK 类库等反射工具类都可以拿到这个类的所有字段,无论字段是不是静态的。

至此,类的生命周期中第一阶段:类加载,已经完成了。

-XX:+TraceClassLoading 通过这个参数可以追踪加载的类

JVM系列 —— 类的生命周期之类加载

https://caolizhi.top/2021-12-JVM系列-类加载/

作者

操先森

发布于

2021-12-01

更新于

2021-12-01

许可协议

评论