JVM(五)启动、加载类过程与类加载器

本文参考自“《Java虚拟机原理图解》5. JVM类加载器机制与类加载过程 ”,对原内容作了些修改并重新组织

JVM启动、加载类过程

下面我将定义一个非常简单的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);  
    }  
}  
  1. 根据JVM内存配置要求,为JVM申请特定大小的内存空间;

    JVM启动时按照其配置要求,申请一块内存,并根据JVM规范和实现将内存划分为几个区域。class二进制文件信息被放入“方法区”,对象实例被放入“java堆”等。关于JVM内存模型内容参考 JVM(一)JVM内存模型

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

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

    引导类加载器将类信息加载到方法区中,以特定方式组织,对于某一个特定的类而言,在方法区中它应该有 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用,对应class实例的引用等信息。

  3. 创建JVM 启动器实例 Launcher,并取得类加载器 ClassLoader

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

  4. 使用上述获取的 ClassLoader 实例加载我们定义的类;

    通过 launcher.getClassLoader() 方法返回 AppClassLoader 实例,接着就是 AppClassLoader 加载 我们自定义类.

    加载自己写的类之前先要加载我们写的类中用到的其他类。在 org.luanlouis.jvm.load.Main 类被编译成的class文件中有一个叫常量池(Constant Pool)的结构体,通过这个常量池中的 CONSTANT_CLASS_INFO 常量判断该class用到了哪些类,并通过类加载器去加载这些类。

  5. 加载完成时候JVM会执行Main类的main方法入口,执行Main类的main方法;

  6. 结束,java程序运行结束,JVM销毁。

类加载器

  • 类加载器的作用:通过一个类的全限定名来获取描述此类的二进制字节流,并将此类相关信息加载到JVM的方法区,并创建一个 java.lang.Class 对象作为此类的访问接口, class 对象的引用也保存在方法区内。
  • 每一个类加载器都有独立的类名称空间。比较两个类是否相等的前提是两个类是由同一个类加载器加载的,否则两个类比不相等。

从JVM角度来讲,只有两种类加载器:启动类加载器其他的类加载器。因为前者是JVM虚拟机的一部分,后者是独立于JVM实现的。

更细致一点划分,类加载器分为下面三种:

1. 启动类加载器(Bootstrap ClassLoader)

启动类加载器是使用C++语言实现的(HotSpot),负责加载JVM虚拟机运行时所需的基本系统级别的类,如java.lang.String, java.lang.Object等等。
启动类加载器(Bootstrap Classloader)会读取 {JRE_HOME}/lib 下的jar包(如 rt.jar)和配置,然后将这些系统类加载到方法区内。
由于类加载器是使用平台相关的底层C/C++语言实现的, 所以该加载器不能被Java代码访问到。但是,我们可以查询某个类是否被引导类加载器加载过。

2. 扩展类加载器 (Extension ClassLoader)

此加载器由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载 {JAVA_HOME}\lib\ext 目录下的类库, 开发者可以直接获取此加载器。
拓展类加载器是是整个JVM加载器的Java代码可以访问到的类加载器的最顶端,即是超级父加载器,拓展类加载器是没有父类加载器的。

3. 应用程序加载器 (Application ClassLoader)

此加载器负责加载用户类路径上指定的类库,若没有指定自定义加载器,则此加载器一般是程序中默认的加载器。
应用类加载器将拓展类加载器当成自己的父类加载器。

4. 用户自定义类加载器(Customized Class Loader)

用户可以自己定义类加载器来加载类。所有的类加载器都要继承 java.lang.ClassLoader 类并重写 findClass(String name) 方法。用户自定义类加载器默认父加载器是 应用程序加载器

各个类加载器关系如下:

双亲委派模型

双亲委派模型工作过程:一个类加载器收到类加载的请求,它首先会把这个请求委派给父类加载器去完成,层层上升,只有当父类加载器无法完成此加载请求时,子加载器才会尝试自己去加载。

要注意的是父加载器和子加载器的关系不是继承关系而是组合关系。子加载器中有一个私有属性 parent 指向父加载器。

具体到上述三个加载器时:当应用程序加载器尝试加载类的时候,首先尝试让其父加载器–拓展类加载器加载;如果拓展类加载器加载成功,则直接返回加载结果 Class<T> instance , 加载失败,则会询问是否引导类加载器已经加载了该类;只有没有加载的时候,应用类加载器才会尝试自己加载。

从源码看双亲委派模型:

public Class<?> loadClass(String name) throws ClassNotFoundException {  
    return loadClass(name, false);  
}  

protected Class<?> loadClass(String name, boolean resolve)  
    throws ClassNotFoundException  
{  
    synchronized (getClassLoadingLock(name)) {  
        // 首先,检查是否已经被当前的类加载器加载,如果已经被加载,直接返回对应的Class<T>实例  
        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;  
    }  
} 

应用程序加载器一般流程如下:

猜你喜欢

转载自blog.csdn.net/wy11933/article/details/80254223