0.前言
OOP-Klass模型
JVM内部基于oop-klass模型描述一个java类,将一个java类分为两个部分进行描述,其中第一个模型是oop,第二个模型是klass。
其中oop用来表示堆中的java对象实例,储存着对象实例的非静态成员变量属性,不包含任何方法;Klass用来表示java类的元数据,包含了java类中声明的方法存在于方法区;oop有klass的引用,如此多个oop实例就不用都保存一份相同的方法信息了。
Klass分类
OOP分类
1.JVM内存模型
2.解析Java文件
public class JvmThread {
public static Integer a;
private Integer b;
static {
a = 128;
}
public JvmThread() {
this.b = 10;
}
public void printf() {
System.out.println(b);
}
public static void main(String[] args) {
System.out.println(JvmThread.a);
JvmThread jvmThread = new JvmThread();
jvmThread.printf();
}
}
javac JvmThread.java => 得到JvmThread.class 文件
javap -c -v -p JvmThread.class => 得到class 文件布局
public class jvm.JvmThread
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#24 // java/lang/Object."<init>":()V
#2 = Methodref #25.#26 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#3 = Fieldref #7.#27 // jvm/JvmThread.b:Ljava/lang/Integer;
#4 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #30.#31 // java/io/PrintStream.println:(Ljava/lang/Object;)V
#6 = Fieldref #7.#32 // jvm/JvmThread.a:Ljava/lang/Integer;
#7 = Class #33 // jvm/JvmThread
#8 = Methodref #7.#24 // jvm/JvmThread."<init>":()V
#9 = Methodref #7.#34 // jvm/JvmThread.printf:()V
#10 = Class #35 // java/lang/Object
#11 = Utf8 a
#12 = Utf8 Ljava/lang/Integer;
#13 = Utf8 b
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 printf
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = Utf8 <clinit>
#22 = Utf8 SourceFile
#23 = Utf8 JvmThread.java
#24 = NameAndType #14:#15 // "<init>":()V
#25 = Class #36 // java/lang/Integer
#26 = NameAndType #37:#38 // valueOf:(I)Ljava/lang/Integer;
#27 = NameAndType #13:#12 // b:Ljava/lang/Integer;
#28 = Class #39 // java/lang/System
#29 = NameAndType #40:#41 // out:Ljava/io/PrintStream;
#30 = Class #42 // java/io/PrintStream
#31 = NameAndType #43:#44 // println:(Ljava/lang/Object;)V
#32 = NameAndType #11:#12 // a:Ljava/lang/Integer;
#33 = Utf8 jvm/JvmThread
#34 = NameAndType #18:#15 // printf:()V
#35 = Utf8 java/lang/Object
#36 = Utf8 java/lang/Integer
#37 = Utf8 valueOf
#38 = Utf8 (I)Ljava/lang/Integer;
#39 = Utf8 java/lang/System
#40 = Utf8 out
#41 = Utf8 Ljava/io/PrintStream;
#42 = Utf8 java/io/PrintStream
#43 = Utf8 println
#44 = Utf8 (Ljava/lang/Object;)V
{
public static java.lang.Integer a;
descriptor: Ljava/lang/Integer;
flags: ACC_PUBLIC, ACC_STATIC
private java.lang.Integer b;
descriptor: Ljava/lang/Integer;
flags: ACC_PRIVATE
public jvm.JvmThread();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 10
7: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
10: putfield #3 // Field b:Ljava/lang/Integer;
13: return
LineNumberTable:
line 19: 0
line 20: 4
line 21: 13
public void printf();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3 // Field b:Ljava/lang/Integer;
7: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
10: return
LineNumberTable:
line 24: 0
line 25: 10
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #6 // Field a:Ljava/lang/Integer;
6: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
9: new #7 // class jvm/JvmThread
12: dup
13: invokespecial #8 // Method "<init>":()V
16: astore_1
17: aload_1
18: invokevirtual #9 // Method printf:()V
21: return
LineNumberTable:
line 28: 0
line 29: 9
line 30: 17
line 31: 21
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: sipush 128
3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: putstatic #6 // Field a:Ljava/lang/Integer;
9: return
LineNumberTable:
line 16: 0
line 17: 9
}
3. 生成一个对象做了什么事情
- 类加载主要有三个过程:loading 、linking 、initializing;
- 其中linking又分为三个步骤:verification 、preparation 、resolution;
3.1 Loading 加载
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("jvm.JvmThread");
JvmThread jvmThread = (JvmThread) aClass.newInstance();
}
}
0: invokestatic #2 // Method java/lang/ClassLoader.getSystemClassLoader:()Ljava/lang/ClassLoader;
3: ldc #3 // String jvm.JvmThread
5: invokevirtual #4 // Method java/lang/ClassLoader.loadClass:(Ljava/lang/String;)Ljava/lang/Class;
8: astore_1
9: aload_1
10: invokevirtual #5 // Method java/lang/Class.newInstance:()Ljava/lang/Object;
13: checkcast #6 // class jvm/JvmThread
16: astore_2
17: return
Loading的过程就是将Java类的字节码文件加载到机器内存中,并在内存中构建出Java类的原型一一类模板对象Klass。
所谓类模板对象,其实就是Java类在JVM内存中的一个快照,JVM将从字节码文件中解析出的常量池、类字段、类方法等信息存储到类模板中,这样JVM在运行期便能通过类模板而获取Java类中的任意信息,能够对Java类的成员变量进行遍历,也能进行Java方法的调用。
在加载类时,Java虚拟机必须完成以下3件事情:
- 通过类的全名,获取类的二进制数据流;
- 解析类的二进制数据流为方法区内的数据结构(Java类模型);
- 创建java.lang.Class类的实例,用于表示该类型,作为方法区这个类的各种数据的访问入口;
双亲委派机制主要流程: Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass(“jvm.JvmThread”);
- 先调用的是AppClassLoader的loadClass 方法
- 判断这个包路径在不在自己管理的范围内
- 判断是否已经加载过了。就不需要加载了
- 调用父类的加载方法(并不是parentClassLoader:ExtClassLoader的加载方法)
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
}
//1.判断这个包路径在不在自己管理的范围内
if (ucp.knownToNotExist(name)) {
//2.判断是否已经加载过了。就不需要加载了
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
throw new ClassNotFoundException(name);
}
//3.调用父类的加载方法(并不是parentClassLoader:ExtClassLoader的加载方法)
return (super.loadClass(name, resolve));
}
//父类的loadClass方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
3.1.1 findLoadedClass:查找这个类是否加载到JVM原数据区里面了。
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
//对应的C++代码
Java_java_lang_ClassLoader_findLoadedClass0(JNIEnv *env, jobject loader,jstring name)
{
if (name == NULL) {
return 0;
} else {
return JVM_FindLoadedClass(env, loader, name);
}
}
JVM_ENTRY(jclass, JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name))
JVMWrapper("JVM_FindLoadedClass");
ResourceMark rm(THREAD);
Handle h_name (THREAD, JNIHandles::resolve_non_null(name));
Handle string = java_lang_String::internalize_classname(h_name, CHECK_NULL);
const char* str = java_lang_String::as_utf8_string(string());
// Sanity check, don't expect null
if (str == NULL) return NULL;
const int str_len = (int)strlen(str);
if (str_len > Symbol::max_length()) {
// It's impossible to create this class; the name cannot fit
// into the constant pool.
return NULL;
}
TempNewSymbol klass_name = SymbolTable::new_symbol(str, str_len, CHECK_NULL);
//在Java方法区字典里面找到对应的类信息指针
Klass* k = SystemDictionary::find_instance_or_array_klass(klass_name,
h_loader,
Handle(),
CHECK_NULL);
//返回类信息指针对应的类信息
return (k == NULL) ? NULL :
(jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END
生成 Class 在 JVM 中的代表:KlassJVM 用来定义一个Java Class 的数据结构。不过Klass只是一个基类,Java Class 真正的数据结构定义在 InstanceKlass中。
InstanceKlass 中记录了一个 Java 类的所有属性,包括注解、方法、字段、内部类、常量池等信息。这些信息本来被记录在Class文件中,所以说,InstanceKlass就是一个Java Class 文件被加载到内存后的形式
class InstanceKlass: public Klass {
protected:
Annotations* _annotations;
......
ConstantPool* _constants;
......
Array<jushort>* _inner_classes;
......
Array<Method*>* _methods;
Array<Method*>* _default_methods;
......
Array<u2>* _fields;
}
3.1.2 findClass 方法:从class文件加载类信息
重点方法: SystemDictionary::resolve_from_stream(class_name, class_loader, protection_domain, &st, verify != 0, CHECK_NULL)
- instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name, loader_data, protection_domain, parsed_name, verify, THREAD): 先解析Class文件
- SystemDictionary::define_instance_class(instanceKlassHandle k, TRAPS) :将class文件解析出来的信息放到jvm里面
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
//最终调用的底层native 方法 byte[] b => 这个类对应的文件字节流
private native Class<?> defineClass0(String name, byte[] b, int off, int len,
ProtectionDomain pd);
//对应的C++ 代码
Java_java_lang_ClassLoader_defineClass0(JNIEnv *env,
jobject loader,
jstring name,
jbyteArray data,
jint offset,
jint length,
jobject pd)
{
return Java_java_lang_ClassLoader_defineClass1(env, loader, name, data, offset,
length, pd, NULL);
}
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;
}
//长度校验
if (length < 0) {
JNU_ThrowArrayIndexOutOfBoundsException(env, 0);
return 0;
}
//分配class文件对应的空间
body = (jbyte *)malloc(length);
if (body == 0) {
JNU_ThrowOutOfMemoryError(env, 0);
return 0;
}
...
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;
}
//(重点)从文件字节流加载对应的class文件,并将class信息放入到jvm方法区
result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);
...
return result;
}
JVM_DefineClassWithSource: 从文件字节流加载对应的class文件,并将class信息放入到jvm方法区
// common code for JVM_DefineClass() and JVM_DefineClassWithSource()
// and JVM_DefineClassWithSourceCond()
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;
....
//加载数据到jvm系统字典里面
Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader,
protection_domain, &st,
verify != 0,
CHECK_NULL);
//JNI 引用管理
return (jclass) JNIHandles::make_local(env, k->java_mirror());
}
SystemDictionary::resolve_from_stream 加载数据到jvm系统字典里面
Klass* SystemDictionary::resolve_from_stream(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
ClassFileStream* st,
bool verify,
TRAPS) {
.....
//解析class文件,类加载后得到Java类对等的instanceKlass
instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name,
loader_data,
protection_domain,
parsed_name,
verify,
THREAD);
....
//class文件信息加载到jvm方法区里面
if (is_parallelCapable(class_loader)) {
k = find_or_define_instance_class(class_name, class_loader, k, THREAD);
} else {
define_instance_class(k, THREAD);
}
....
return k();
}
3.1.3 findBootstrapClassOrNull: bootStrap 类加载器加载
- SystemDictionary::resolve_or_null(h_name, CHECK_NULL); 加载类信息(这个方法篇幅很长,感兴趣的同学可以自己看一下openjdk)
private Class<?> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
private native Class<?> findBootstrapClass(String name);
//findBootstrapClass 方法对应的C++方法
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,
const char* name))
......
TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
//加载对应的类信息
Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
if (k == NULL) {
return NULL;
}
return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END
3.1.4 加载实现的解析工作:类解析器(ClassFileParser)
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,ClassLoaderData* loader_data,Handle protection_domain, KlassHandle host_klass,GrowableArray<Handle>* cp_patches,TempNewSymbol& parsed_name, bool verify,TRAPS) {
......
// Constant pool class常量池解析
constantPoolHandle cp = parse_constant_pool(CHECK_(nullHandle));
// 获取访问标识符
AccessFlags access_flags;
jint flags = cfs->get_u2_fast() & JVM_RECOGNIZED_CLASS_MODIFIERS;
if ((flags & JVM_ACC_INTERFACE) && _major_version < JAVA_6_VERSION) {
// Set abstract bit for old class files for backward compatibility
flags |= JVM_ACC_ABSTRACT;
}
verify_legal_class_modifiers(flags, CHECK_(nullHandle));
access_flags.set_flags(flags);
......
//解析interface
Array<Klass*>* local_interfaces =
parse_interfaces(itfs_len, protection_domain, _class_name,
&has_default_methods, CHECK_(nullHandle));
//解析field 方法区的字段信息
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,
&has_default_methods,
CHECK_(nullHandle));
//获取加载.class文件保存在方法区的元数据instanceKlass
//生成对应的class类 进行对应的字节码解析
_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对象)、版本号...等属性代码
// create_mirror方法用于创建一个java.lang.Class对象,这个对象存储在堆中,也称为InstanceKlass的镜像
// 为klass和class实例互相设置引用,并为静态变量设置默认值以及完成对带final static的属性赋恒定值
java_lang_Class::create_mirror(this_klass, protection_domain, CHECK_(nullHandle));
// ...省略不关键代码
return this_klass;
......
}
3.2 Linking
Linking链接阶段: 链接阶段主要包含验证、准备、解析三个步骤
虽然加载过程在链接过程之前,但是虚拟机规范并未规定具体实现必须按照这一顺序执行,HotSpot就是将一些链接过程细节提前到加载阶段实现(ClassFileParser.parse)。例如:格式检查和语义检查
3.2.1 Verification(验证)
目的在于确保class 文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机的自身安全
主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
InstaceKlass::link_class_impl(),源码很长,主要有5个步骤:
1)字节码验证(verify_code);
2)字节码重写(rewrite_class);
3)方法链接(link_method);
4)初始化vtable(虚表)和itable(接口表);
5)链接完成(set_init_state)。
3.2.1.1 格式校验
格式验证:是否以魔数OxCAFEBABE开头,主版本和副版本号是否在当前Java虚拟机的支持范围内,数据中每一个项是否都拥有正确的长度等;
上文不是提到了ClassFileParser(st).parseClassFile(class_name, loader_data, protection_domain, parsed_name, verify, THREAD); 方法:(其实是解析class文件过程中进行了Verification的格式校验和语义校验)
- 其中格式验证会和加载阶段一起执行。验证通过之后,类加载器才会成功将类的二进制数据信息加载到方法区中;
- 格式验证之外的验证操作将会在方法区中进行;
链接阶段的验证虽然拖慢了加载速度,但是它避免了在字节码运行时还需要进行各种检查。
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,ClassLoaderData* loader_data,Handle protection_domain, KlassHandle host_klass,GrowableArray<Handle>* cp_patches,TempNewSymbol& parsed_name, bool verify,TRAPS) {
......
//魔数校验
u4 magic = cfs->get_u4_fast();
guarantee_property(magic == JAVA_CLASSFILE_MAGIC,
"Incompatible magic value %u in class file %s",
magic, CHECK_(nullHandle));
// Version numbers
u2 minor_version = cfs->get_u2_fast();
u2 major_version = cfs->get_u2_fast();
//查看java版本支不支持 java版本检查
if (!is_supported_version(major_version, minor_version)) {
.....
return nullHandle;
}
......
}
3.2.1.2 语义检查
Java虚拟机会进行字节码的语义检查,但凡在语义上不符合规范的,虚拟机也不会给予验证通过。比如:
- 是否所有的类都有父类的存在(在Java里,除了0bject外,其他类都应该有父类);
- 是否一些被定义为final的方法或者类被重写或继承;
- 非抽象类是否实现了所有抽象方法或者接口方法;
- 是否存在不兼容的方法(比如方法的签名除了返回值不同,其他都一样,这种方法会让虚拟机无从下手调度;abstract情况下的方法,就不能是final的了);
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,ClassLoaderData* loader_data,Handle protection_domain, KlassHandle host_klass,GrowableArray<Handle>* cp_patches,TempNewSymbol& parsed_name, bool verify,TRAPS) {
......
//跟着上面的方法继续解析
u2 super_class_index = cfs->get_u2_fast();
//解析父类,判断是否有接口需要实现
instanceKlassHandle super_klass = parse_super_class(super_class_index, CHECK_NULL);
//是否有父类
if (super_klass.not_null()) {
if (super_klass->has_default_methods()) {
has_default_methods = true;
}
if (super_klass->is_interface()) {
ResourceMark rm(THREAD);
Exceptions::fthrow(
THREAD_AND_LOCATION,
vmSymbols::java_lang_IncompatibleClassChangeError(),
"class %s has interface %s as super class",
class_name->as_klass_external_name(),
super_klass->external_name()
);
return nullHandle;
}
// 是否继承final
if (super_klass->is_final()) {
THROW_MSG_(vmSymbols::java_lang_VerifyError(), "Cannot inherit from final class", nullHandle);
}
}
}
3.2.1.3 字节码验证
Java虚拟机还会进行字节码验证,字节码验证也是验证过程中最为复杂的一个过程。它试图通过对字节码流的分析,判断字节码是否可以被正确地执行。比如:
- 在字节码的执行过程中,是否会跳转到一条不存在的指令;
- 函数的调用是否传递了正确类型的参数;
- 变量的赋值是不是给了正确的数据类型等;
- 栈映射帧(StackMapTable)就是在这个阶段,用于检测在特定的字节码处,其局部变量表和操作数栈是否有着正确的数据类型。
但遗憾的是,100%准确地判断一段字节码是否可以被安全执行是无法实现的,因此,该过程只是尽可能地检查出可以预知的明显的问题。如果在这个阶段无法通过检查,虚拟机也不会正确装载这个类。但是,如果通过了这个阶段的检查,也不能说明这个类是完全没有问题的。
//方法对应反汇编信息
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #6 // Field a:Ljava/lang/Integer;
6: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
9: new #7 // class jvm/JvmThread
12: dup
13: invokespecial #8 // Method "<init>":()V
16: astore_1
17: aload_1
18: invokevirtual #9 // Method printf:()V
21: return
LineNumberTable:
line 28: 0
line 29: 9
line 30: 17
line 31: 21
//入口是define_instance_class 方法 ,进行class类进行链接
bool InstanceKlass::link_class_impl(
instanceKlassHandle this_oop, bool throw_verifyerror, TRAPS);
......
//link_class_impl方法有校验字节码的方法
bool verify_ok = verify_code(this_oop, throw_verifyerror, THREAD);
if (!verify_ok) {
return false;
}
......
//verify_code 里面主要调用这个方法verify_method,验证每个方法的操作栈帧,局部变量表是否正常
void ClassVerifier::verify_method(methodHandle m, TRAPS) {
...
//检查局部变量表
if (m->has_localvariable_table()) {
verify_local_variable_table(code_length, code_data, CHECK_VERIFY(this));
}
//检查操作数栈的类型是否正确
case Bytecodes::_iconst_5 :
current_frame.push_stack(
VerificationType::integer_type(), CHECK_VERIFY(this));
...
}
在前面3次检查中,已经排除了文件格式错误、语义错误以及字节码的不正确性。但是依然不能确保类是没有问题的。
3.2.1.4 符号引用验证
校验器还将进行符号引用的验证。符号引用转化为直接引用的时候,可以看作是对类自身以外的信息进行匹配验证,如通过全限定名,是否能找到对应的类。
因此,在验证阶段,虚拟机就会检查这些类或者方法确实是存在的,并且当前类有权限访问这些数据,如果一个需要使用的类无法在系统中找到,则会抛出NoClassDefFoundError,如果一个方法无法被找到,则会抛出NoSuchMethodError。
注意【符号引用的验证】在解析环节才会执行。
// relocate jsrs and link methods after they are all rewritten
// 属于解析阶段的动作
this_oop->link_methods(CHECK_false);
void InstanceKlass::link_methods(TRAPS) {
int len = methods()->length();
for (int i = len-1; i >= 0; i--) {
methodHandle m(THREAD, methods()->at(i));
// Set up method entry points for compiler and interpreter .
m->link_method(m, CHECK);
}
}
//在Method::link_method()函数中为方法设置对应的入口entry,
//对于本地方法设置native入口,对于本地同步方法设置native_synchronized入口。
void Method::link_method(methodHandle h_method, TRAPS) {
if (_i2i_entry != NULL) return;
assert(_adapter == NULL, "init'd to NULL" );
assert( _code == NULL, "nothing compiled yet" );
// 判断对应的符号引用是否对应的上
assert(this == h_method(), "wrong h_method()" );
// 设置解释执行的入口
address entry = Interpreter::entry_for_method(h_method);
assert(entry != NULL, "interpreter entry must be non-null");
//将解释执行的入口保存到_i2i_entry和_from_interpreted_entry
set_interpreter_entry(entry);
// Don't overwrite already registered native entries.
if (is_native() && !has_native_function()) {
set_native_function(
SharedRuntime::native_method_throw_unsatisfied_link_error_entry(),
!native_bind_event_is_interesting);
}
// 设置编译执行的入口
(void) make_adapters(h_method, CHECK);
}
3.2.2 准备阶段 (给静态变量赋初值)
类的状态如下:
准备阶段是为类中定义的静态变量分配内存并设置初始化值的阶段,这里的初始值通常下指的是对应类型的零值,比如int类型的零值为0
类加载器 ClassFileParser::parseClassFile 做了以下操作:
- allocate_instance_klass:初始化一个空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)){
......
// normal class 初始化一个空instanceKlass对象,并由后续逻辑进行数据的填充,并标志其状态为已分配。
ik = new (loader_data, size, THREAD) InstanceKlass(vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt, access_flags, is_anonymous);
.....
}
InstanceKlass::InstanceKlass(int vtable_len,
int itable_len,
int static_field_size,
int nonstatic_oop_map_size,
ReferenceType rt,
AccessFlags access_flags,
bool is_anonymous) {
...
//标志为已分配
set_init_state(InstanceKlass::allocated);
...
}
- java_lang_Class::create_mirror:为静态变量设置默认值
do_local_static_fields 会对静态字段进行初始化。 注意此是传入的函数指针表示的 initialize_static_field 函数, do_local_static_fields 会在内部遍历所有静态字段,然后调用这个函数对他们进行初始化
oop java_lang_Class::create_mirror(KlassHandle k, Handle protection_domain, TRAPS) {
//allocate_instance 内部会根据 k 中的信息,计算需要分配的空间,包含静态变量的大小。然后对 mirror 的空间进行分配
Handle mirror = InstanceMirrorKlass::cast(SystemDictionary::Class_klass())->allocate_instance(k, CHECK_0);
// Initialize static fields
InstanceKlass::cast(k())->do_local_static_fields(&initialize_static_field, CHECK_NULL);
return mirror();
}
//初始化静态变量
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");
if (fd->has_initial_value()) {
BasicType t = fd->field_type();
switch (t) {
case T_BYTE:
//ClassFileParser::parse_fields 方法之前对 field 设置的 offset
//再根据offset,去设置对应的值,这样就知道了静态变量存储在哪里
mirror()->byte_field_put(fd->offset(), fd->int_initial_value());
break;
...
}
}
}
//从常量池获取初始值
jint fieldDescriptor::int_initial_value() const {
return constants()->int_at(initial_value_index());
}
inline void oopDesc::byte_field_put(int offset, jbyte contents) { *byte_field_addr(offset) = (jint) contents; }
3.2.3 解析阶段
虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程 :静态链接
动态链接在栈帧里面,不会在停留在方法区里面,是跟线程有关系的
可看这篇文章:Java 常量池详解(二)class文件常量池 和 Java 常量池详解(三)class运行时常量池
// 入口 define_instance_class 方法
//在Method::link_method()函数中为方法设置对应的入口entry,
//对于本地方法设置native入口,对于本地同步方法设置native_synchronized入口。
void Method::link_method(methodHandle h_method, TRAPS) {
if (_i2i_entry != NULL) return;
assert(_adapter == NULL, "init'd to NULL" );
assert( _code == NULL, "nothing compiled yet" );
// 判断对应的符号引用是否对应的上
assert(this == h_method(), "wrong h_method()" );
// 设置解释执行的入口
address entry = Interpreter::entry_for_method(h_method);
assert(entry != NULL, "interpreter entry must be non-null");
//将解释执行的入口保存到_i2i_entry和_from_interpreted_entry
set_interpreter_entry(entry);
// Don't overwrite already registered native entries.
if (is_native() && !has_native_function()) {
set_native_function(
SharedRuntime::native_method_throw_unsatisfied_link_error_entry(),
!native_bind_event_is_interesting);
}
// 设置编译执行的入口
(void) make_adapters(h_method, CHECK);
}
//执行完linked方法之后
bool InstanceKlass::link_class_impl(
instanceKlassHandle this_oop, bool throw_verifyerror, TRAPS) {
//设置连接状态
this_oop->set_init_state(linked);
}
3.3 初始化
前面提到的define_instance_class 方法执行了 eager_initialize ,eager_initialize 里面进行了链接操作eager_initialize_impl
void InstanceKlass::eager_initialize(Thread *thread) {
if (!EagerInitialization) return;
//如果这个类还没进行初始化状态
if (this->is_not_initialized()) {
if (this->class_initializer() != NULL) return;
Klass* super = this->super();
if (super == NULL) return;
// 判断父类是否初始化完成
if (!InstanceKlass::cast(super)->is_initialized()) return;
// 进行初始化操作
instanceKlassHandle this_oop(thread, this);
eager_initialize_impl(this_oop);
}
}
void InstanceKlass::eager_initialize_impl(instanceKlassHandle this_oop) {
link_class_impl(this_oop, true, THREAD);
...
//链接完成之后,标志类初始化完成
this_oop->set_init_state (fully_initialized);
}
class.forName() 方法解析:验证类初始化标志判断
//对应的java
Class.forName("com.demo.ha.config.simple.SimpleLogProducer");
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
//对应的c++
//对应的C++代码
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
jboolean initialize, jobject loader, jclass caller){
//重点调用了这个方法
return JVM_FindClassFromCaller(env, clname, initialize, loader, caller);
}
JVM_ENTRY(jclass, JVM_FindClassFromCaller(JNIEnv* env, const char* name,
jboolean init, jobject loader,
jclass caller)){
//重点调用了这个方法,根据类加载器查找对应的类
jclass result = find_class_from_class_loader(env, h_name, init, h_loader,
h_prot, false, THREAD);
}
判断类是否初始化完成 (this->should_be_initialized ) ,没有则进行类的初始化动作( initialize_impl)
jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init, Handle loader, Handle protection_domain, jboolean throwError, TRAPS) {
//查找对应的class
Klass* klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL);
KlassHandle klass_handle(THREAD, klass);
// Check if we should initialize the class
if (init && klass_handle->oop_is_instance()) {
//判断类是否初始化完成,没有进行类的初始化
klass_handle->initialize(CHECK_NULL);
}
return (jclass) JNIHandles::make_local(env, klass_handle->java_mirror());
}
void InstanceKlass::initialize(TRAPS) {
//判断类是否初始化完毕。
if (this->should_be_initialized()) {
HandleMark hm(THREAD);
instanceKlassHandle this_oop(THREAD, this);
//进行类的初始化过程
initialize_impl(this_oop, CHECK);
} else {
assert(is_initialized(), "sanity check");
}
}
4 给类分配空间
JvmThread jvmThread = (JvmThread) aClass.newInstance();
//调用的Unsafe类的native方法
public native Object allocateInstance(Class<?> cls) throws InstantiationException;
对应的cpp
static instanceOop alloc_object(jclass clazz, TRAPS) {
KlassHandle k(THREAD, java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
k()->check_valid_for_instantiation(false, CHECK_NULL);
InstanceKlass::cast(k())->initialize(CHECK_NULL);
instanceOop ih = InstanceKlass::cast(k())->allocate_instance(THREAD);
return ih;
}
instanceOop InstanceMirrorKlass::allocate_instance(KlassHandle k, TRAPS) {
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;
}
oop CollectedHeap::Class_obj_allocate(KlassHandle klass, int size, KlassHandle real_klass, TRAPS) {
HeapWord* obj;
assert(ScavengeRootsInCode > 0, "must be");
//分配内存空间
obj = common_mem_allocate_init(real_klass, size, CHECK_NULL);
}
4.1 分配内存阶段
- 快速分配:
如果在实例分配之前已经完成了类型的解析,那么分配操作仅仅是在内存空间划分可用内存,因此能比较快实现内存分配。- TLAB:线程局部分配缓存技术(可通过UseTLAB选择开启或者关闭功能)
- Eden:直接在Eden申请分配空间
HeapWord* CollectedHeap::common_mem_allocate_init(KlassHandle klass, size_t size, TRAPS) {
//分配内存空间
HeapWord* obj = common_mem_allocate_noinit(klass, size, CHECK_NULL);
//完成对对象内存空间的对齐和填充
init_obj(obj, size);
return obj;
}
HeapWord* CollectedHeap::common_mem_allocate_noinit(KlassHandle klass, size_t size, TRAPS) {
if (UseTLAB) {
result = allocate_from_tlab(klass, THREAD, size);
if (result != NULL) {
assert(!HAS_PENDING_EXCEPTION,
"Unexpected exception, will result in uninitialized storage");
return result;
}
}
bool gc_overhead_limit_was_exceeded = false;
result = Universe::heap()->mem_allocate(size, &gc_overhead_limit_was_exceeded);
}
TLAB,全称 Thread Local Allocation Buffer,即线程本地分配缓存。是一块 线程专用 的内存分配区域。TLAB占用的是 eden 区的空间,在 TLAB 启用的情况下(默认开启),JVM 会为每一个线程分配一块私有缓存区域区域,即为 TLAB 内存区域。
TLAB的好处:
- 加速对象的分配。由于对象一般分配在堆上,而堆是线程共用的,因此可能会有多个线程在堆上申请空间,而每一次的对象分配都必须线程同步,会使分配的效率下降
- 多线程同时分配内存时,使用 TLAB 可以 避免一系列的非线程安全问题,同时还能够 提升内存分配的吞吐量,因此我们可以将这种内存分配方式称为 快速分配策略。
ParallelScavengeHeap就是开启UseParallelGC时的GC实现,G1CollectedHeap是开启UseG1GC时的GC实现,GenCollectedHeap是开启UseSerialGC或者UseConcMarkSweepGC的GC实现,分别对应两种不同的GenCollectorPolicy。
- 慢速分配:
在分配之前需要对类进行解析,调用InterperterRuntime 模块_new()进行的,实现代码如下:
IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))
Klass* k_oop = pool->klass_at(index, CHECK);
instanceKlassHandle klass (THREAD, k_oop);
//确保要初始化的类不是抽象类
klass->check_valid_for_instantiation(true, CHECK);
//确保类已经初始化
klass->initialize(CHECK);
//分配实例
oop obj = klass->allocate_instance(CHECK);
thread->set_vm_result(obj);
IRT_END