Java虚拟机启动、加载类过程分析

转载自:https://blog.csdn.net/luanlouis/article/details/50529868
  
  首先定义一个非常简单的java程序并运行它,来逐步分析java虚拟机启动的过程。

package org.luanlouis.jvm.load;
import sun.security.pkcs11.P11Util;

/**
 * Created by louis on 2016/1/16.
 */
public class Main{

    public static void main(String[] args) {
        System.out.println("Hello,World!");

        ClassLoader loader = P11Util.class.getClassLoader();
     
        System.out.println(loader);
    }
}

  在windows命令行下java org.luanlouis.jvm.load.Main后,windows开始运行{JRE_HOME}/bin/java.exe程序,java.exe 程序将完成以下步骤:

  1. 根据JVM内存配置要求,为JVM申请特定大小的内存空间;
  2. 创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;
  3. 创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader;
  4. 使用上述获取的ClassLoader实例加载我们定义的 org.luanlouis.jvm.load.Main类;
  5. 加载完成时候JVM会执行Main类的main方法入口,执行Main类的main方法;
  6. 结束,java程序运行结束,JVM销毁。

  

Step 1.根据JVM内存配置要求,为JVM申请特定大小的内存空间

  JVM启动时,按功能划分,其内存应该由以下几部分组成:

  如上图所示,JVM内存按照功能上的划分,可以粗略地划分为方法区(Method Area) 和堆(Heap)。

Step 2. 创建一个引导类加载器实例,初步加载系统类到内存方法区区域中;

  JVM申请好内存空间后,JVM会创建一个引导类加载器(Bootstrap Classloader)实例,引导类加载器是使用C++语言实现的,负责加载JVM虚拟机运行时所需的基本系统级别的类,如java.lang.String, java.lang.Object等等。
  引导类加载器(Bootstrap Classloader)会读取 {JRE_HOME}/lib 下的jar包和配置,然后将这些系统类加载到方法区内。

  引导类加载器(Bootstrap ClassLoader) 加载系统类后,JVM内存会呈现如下格局:

  • 引导类加载器将类信息加载到方法区中,以特定方式组织,对于某一个特定的类而言,在方法区中它应该有 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用,对应class实例的引用等信息。
  • 类加载器的引用,由于这些类是由引导类加载器(Bootstrap Classloader)进行加载的,而 引导类加载器是有C++语言实现的,所以是无法访问的,故而该引用为NULL
  • 对应class实例的引用, 类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。
Step 3. 创建JVM 启动器实例 Launcher,并取得类加载器ClassLoader

  上述步骤完成,JVM基本运行环境就准备就绪了。接着,要让JVM工作起来了:运行org.luanlouis,jvm.load.Main。

  此时,JVM虚拟机调用已经加载在方法区的类sun.misc.Launcher 的静态方法getLauncher(), 获取sun.misc.Launcher 实例:

sun.misc.Launcher launcher = sun.misc.Launcher.getLauncher(); //获取Java启动器
ClassLoader classLoader = launcher.getClassLoader();          //获取类加载器ClassLoader用来加载class到内存来

  sun.misc.Launcher 使用了单例模式设计,保证一个JVM虚拟机内只有一个sun.misc.Launcher实例。
  在Launcher的内部,其定义了两个类加载器(ClassLoader),分别是sun.misc.Launcher.ExtClassLoadersun.misc.Launcher.AppClassLoaderlauncher.getClassLoader()方法将会返回 AppClassLoader 实例,AppClassLoader将ExtClassLoader作为自己的父加载器。
  当AppClassLoader加载类时,会首先尝试让父加载器ExtClassLoader进行加载,如果父加载器ExtClassLoader加载成功,则AppClassLoader直接返回父加载器ExtClassLoader加载的结果;如果父加载器ExtClassLoader加载失败,AppClassLoader则会判断该类是否是引导的系统类(即是否是通过Bootstrap类加载器加载,这会调用Native方法进行查找);若要加载的类不是系统引导类,那么ClassLoader将会尝试自己加载,加载失败将会抛出“ClassNotFoundException”。
  这里说到的应用类加载器AppClassLoader的加载类的模式就是常说的双亲委派模型(parent-delegation model)。对于某个特定的类加载器而言,应该为其指定一个父类加载器,当用其进行加载类的时候:

  1. 委托父类加载器帮忙加载;
  2. 父类加载器加载不了,则查询引导类加载器有没有加载过该类;
  3. 如果引导类加载器没有加载过该类,则当前的类加载器应该自己加载该类;
  4. 若加载成功,返回 对应的Class 对象;若失败,抛出异常“ClassNotFoundException”。
Step 4. 使用类加载器ClassLoader加载Main类

  通过 launcher.getClassLoader()方法返回AppClassLoader实例,接着就是AppClassLoader加载 org.luanlouis.jvm.load.Main类的时候了。

ClassLoader classloader = launcher.getClassLoader();//取得AppClassLoader类
classLoader.loadClass("org.luanlouis.jvm.load.Main");//加载自定义类

  上述定义的org.luanlouis.jvm.load.Main类被编译成org.luanlouis.jvm.load.Main class二进制文件,这个class文件中有一个叫常量池(Constant Pool)的结构体来存储该class的常亮信息。常量池中有CONSTANT_CLASS_INFO类型的常量,表示该class中声明了要用到那些类:

  当AppClassLoader要加载 org.luanlouis.jvm.load.Main类时,会去查看该类的定义,发现它内部声明使用了其它的类: sun.security.pkcs11.P11Util、java.lang.Object、java.lang.System、java.io.PrintStream、java.lang.Class;org.luanlouis.jvm.load.Main类要想正常工作,首先要能够保证这些其内部声明的类加载成功。所以AppClassLoader要先将这些类加载到内存中。(注:为了理解方便,这里没有考虑懒加载的情况,事实上的JVM加载类过程比这复杂的多)

加载顺序:

  1. 加载java.lang.Object、java.lang.System、java.io.PrintStream、java,lang.Class
    AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现不是其加载范围,其返回null;AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过,结果表明这些类已经被BootstrapClassLoader加载过,则无需重复加载,直接返回对应的Class实例;

  2. 加载sun.security.pkcs11.P11Util
    此在{JRE_HOME}/lib/ext/sunpkcs11.jar包内,属于ExtClassLoader负责加载的范畴。AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现其正好属于加载范围,故ExtClassLoader负责将其加载到内存中。ExtClassLoader在加载sun.security.pkcs11.P11Util时也分析这个类内都使用了哪些类,并将这些类先加载内存后,才开始加载sun.security.pkcs11.P11Util,加载成功后直接返回对应的Class<sun.security.pkcs11.P11Util>实例;

  3. 加载org.luanlouis.jvm.load.Main
    AppClassLoader尝试加载这些类的时候,会先委托ExtClassLoader进行加载;而ExtClassLoader发现不是其加载范围,其返回null;AppClassLoader发现父类加载器ExtClassLoader无法加载,则会查询这些类是否已经被BootstrapClassLoader加载过。而结果表明BootstrapClassLoader 没有加载过它,这时候AppClassLoader只能自己动手负责将其加载到内存中,然后返回对应的Class<org.luanlouis.jvm.load.Main>实例引用;

以上三步骤都成功,才表示classLoader.loadClass(“org.luanlouis.jvm.load.Main”)完成。

Step 5. 使用Main类的main方法作为程序入口运行程序

  具体方法如何执行,请参见:https://blog.csdn.net/luanlouis/article/details/50412126

Step 6. 方法执行完毕,JVM销毁,释放内存
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值