基于jvm源码解析类加载过程

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. 生成一个对象做了什么事情

在这里插入图片描述

  • 类加载主要有三个过程:loadinglinkinginitializing
  • 其中linking又分为三个步骤:verificationpreparationresolution

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虚拟机还会进行字节码验证,字节码验证也是验证过程中最为复杂的一个过程。它试图通过对字节码流的分析,判断字节码是否可以被正确地执行。比如:

  1. 在字节码的执行过程中,是否会跳转到一条不存在的指令;
  2. 函数的调用是否传递了正确类型的参数;
  3. 变量的赋值是不是给了正确的数据类型等;
  4. 栈映射帧(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

资料参考:

类的链接
第7.6篇-类的初始化
HotSpot 实战

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值