从JDK源码级别深度解析JVM类加载机制

运行main方法启动程序时,需要通过类加载器将这个类加载到JVM内存中。

加载过程

加载:在硬盘中查找并通过IO读入字节码文件,只加载使用到的类;

验证:是否符合JVM的要求规则;

准备:为静态变量分配内存,并且赋值为默认值;

public static int number = 66;

实际上变量 number 在准备阶段过后的初始值为 0 而不是66;

如果是final修饰的,那么在编译阶段会为 number 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 number赋值为 66。

解析:引用符号替换为直接符号,简单的说就是静态方法替换为指向数据所存内存的指针;

初始化:为静态变量初始化指定的值,执行静态代码块。

public class Test {
    
    public static final int intNum = 100;
    public static Student student = new Student();
    static {        
        System.out.println("this is the static of Test");
    }
    public static void main(String[] args) {
        Test test = new Test();
        test.methodOne();
    }
    public void methodOne(){
        System.out.println("this is the first method");
    }
    public static void methodTwo(){
        System.out.println("this is the second method");
    }
    public  void methodThree(){
        System.out.println("this is the third method");
    }
}

public class Student {
    static {
        System.out.println("this is a static of Student");
    }
    public Student(){
        System.out.println("this is a Object of Student");
    }
}

输出结果:

this is a static of Student
this is a Object of Student
this is the static of Test
this is the first method

从运行结果看出:首先初始化静态变量,然后再执行静态代码块,最后执行main方法,并且没有用到的方法都不会去加载。

代码执行流程图如下:

从JDK源码级别深度解析JVM类加载机制_第1张图片

类加载器:

1.引导类加载器(BootstrapClassLoader):加载位于JRE的lib目录下的核心类库,如rt.jar、charsets.jar等。

2.扩展类加载器(ExtClassLoader):加载位于JRE的lib目录下的ext目录中的jar包。

3.应用类加载器(AppClassloader):加载ClassPath目录下的类,就是自己写的类。

示例代码:

public class JDKTest01 {
    public static void main(String[] args) {
        System.out.println("Hello World!");
        System.out.println("引导类加载器:"+String.class.getClassLoader());
        System.out.println("扩展类加载器:"+CurveDB.class.getClassLoader().getClass().getName());
        System.out.println("应用类加载器:"+JDKTest01.class.getClassLoader().getClass().getName());

        System.out.println("");

        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassLoader = appClassLoader.getParent();
        ClassLoader bootsrapClassLoader = extClassLoader.getParent();
        System.out.println("引导类加载器:"+ bootsrapClassLoader);
        System.out.println("扩展类加载器:"+ extClassLoader);
        System.out.println("应用类加载器:"+ appClassLoader);
        System.out.println("");
        System.out.println("引导类加载器加载的类文件如下:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
       for (int i = 0;i

运行结果:
Hello World!
引导类加载器:null
扩展类加载器:sun.misc.Launcher$ExtClassLoader
应用类加载器:sun.misc.Launcher$AppClassLoader

引导类加载器:null
扩展类加载器:sun.misc.Launcher$ExtClassLoader@29453f44
应用类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2

引导类加载器加载的类文件如下:
file:/D:/Java/jdk1.8.0_131/jre/lib/resources.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/rt.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/sunrsasign.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/jsse.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/jce.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/charsets.jar
file:/D:/Java/jdk1.8.0_131/jre/lib/jfr.jar
file:/D:/Java/jdk1.8.0_131/jre/classes

扩展类加载器加载的类文件如下:
D:\Java\jdk1.8.0_131\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

应用类加载器加载的类文件如下:
D:\Java\jdk1.8.0_131\jre\lib\charsets.jar
D:\Java\jdk1.8.0_131\jre\lib\deploy.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar
D:\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar
D:\Java\jdk1.8.0_131\jre\lib\javaws.jar
D:\Java\jdk1.8.0_131\jre\lib\jce.jar
D:\Java\jdk1.8.0_131\jre\lib\jfr.jar
D:\Java\jdk1.8.0_131\jre\lib\jfxswt.jar
D:\Java\jdk1.8.0_131\jre\lib\jsse.jar
D:\Java\jdk1.8.0_131\jre\lib\management-agent.jar
D:\Java\jdk1.8.0_131\jre\lib\plugin.jar
D:\Java\jdk1.8.0_131\jre\lib\resources.jar
D:\Java\jdk1.8.0_131\jre\lib\rt.jar
E:\ProjectsIdea\out\production\ProjectsIdea
D:\Program Files\JetBrains\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar

类加载器初始化过程:

C++调用java代码创建JVM启动器,即实例化sun.misc.Launcher(单例模式),源码如:

  private static Launcher launcher = new Launcher();
    ...
    public static Launcher getLauncher() {
        return launcher;
    }

在sun.misc.Launcher的构造方法中创建了两个类加载器:sun.misc.Launcher.AppClassLoader 和 sun.misc.Launcher.ExtClassloader。JVM默认使用Launcher的getLauncher()方法返回的类加载器AppClassLoader的实例加载我们自己的应用程序。Launcher的构造方法源码如下:

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if(var2 != null) {
            SecurityManager var3 = null;
            if(!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                    ;
                } catch (InstantiationException var6) {
                    ;
                } catch (ClassNotFoundException var7) {
                    ;
                } catch (ClassCastException var8) {
                    ;
                }
            } else {
                var3 = new SecurityManager();
            }

            if(var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

双亲委派机制:

JVM类加载器之间关系是亲子层级结构的,如下图

从JDK源码级别深度解析JVM类加载机制_第2张图片

双亲委派机制,即加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器都在自己的加载类路径下都找不到目标类,那么就在自己的类加载路径中查到且载入目标类。 简而言之,先找父加载器加载,找不到则自己来加载。

下面请看加载器AppClassLoader加载类的双亲委派源码:

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.
                    //如果仍然没有找到,那么按顺序调用findClass去找那个类
                    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;
        }
    }

 双亲委托机制设计的目的:(为什么要这么设计?)

1.沙箱安全机制:我们自己写的类的类名(包括路径)与核心API库中类名(包括路径)相同时,我们自己写这个类不会被加载,这样就可以防止核心类库被篡改。

2.避免重复加载:当父加载器已经加载了该类时,子加载器就没必要再加载一次,保证被加载类的唯一性。

下面请看一个示例:

package java.lang;

/**
 * Created
 */
public class Math {
 public static void main(String[] args) {
     System.out.println("=============进入Math类===========");
 }
}
        运行结果:
 
        错误: 在类 java.lang.Math 中找不到 main 方法, 请将 main 方法定义为:  public static void main(String[] args)
 
 否则 JavaFX 应用程序类必须扩展javafx.application.Application
 

为什么会报错?就是因为双亲委托机制,加载的类是核心库中的java.lang.Math,而这个类是没有main方法的。

全盘负责委托机制(也可称为当前类加载器负责机制):

       “全盘负责”是指当一个ClassLoader装载一个类时,除非显示地使用另一个ClassLoader,则该类所依赖及引用的类也由这个
 
CladdLoader载入。例如,系统类加载器AppClassLoader加载入口类(含有main方法的类)时,会把main方法所依赖的类及引用
 
的类也载入。
 

自定义类加载器:

负责加载用户自定义路径下的类包。自定义加载器只需要继承java.lang.ClassLoader类,该类有两个核心方法:loadClass(String,boolean),实现了双亲委派机制,还有个方法是findClass,默认实现是空方法。

下面是自定义加载器示例:

import java.io.FileInputStream;
import java.lang.reflect.Method;

/**
 * Created 
 */
public class MyClassLoader {
    static class MyClassLoader1 extends ClassLoader{
        private String classPath;

        public MyClassLoader1(String classPath){
            this.classPath = classPath;

        }

        private byte[] loadByte(String name)throws Exception{
            name = name.replaceAll("\\.","/");
            FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        @Override
        protected Class findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name,data,0,data.length);
            }catch (Exception e){
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
        public static void main(String[] arg) throws Exception{
            //初始化自定义类加载器,会先初始化父类ClassLoader,
            // 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
            MyClassLoader1 classLoader1 = new MyClassLoader1("E:/jvm");
           //E盘创建jvm/com/huigu/stu目录,将Student类的复制类Student1.class丢入该目录
            Class clazz = classLoader1.loadClass("com.huigu.stu.Student1");
            Object obj = clazz.newInstance();

            Method method = clazz.getDeclaredMethod("study",null);
            method.invoke(obj,null);

            System.out.println(clazz.getClassLoader().getClass().getName());

        }     

}

public class Student {
    private String name;
    public void study(){
        System.out.println("好好学习,天天向上");
    }
}

运行结果:

好好学习,天天向上
       test01.MyClassLoader$MyClassLoader1

打破双亲委派机制 

技术实现:重写类加载方法,需要自定义类加载器加载的类不委派给双亲加载。代码示例如下:

import java.io.FileInputStream;
import java.lang.reflect.Method;

/**
 * Created
 */
public class MyClassLoader {
    static class MyClassLoader1 extends ClassLoader{
        private String classPath;

        public MyClassLoader1(String classPath){
            this.classPath = classPath;

        }

        private byte[] loadByte(String name)throws Exception{
            name = name.replaceAll("\\.","/");
            FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        @Override
        protected Class findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name,data,0,data.length);
            }catch (Exception e){
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
        public static void main(String[] arg) throws Exception{
            //初始化自定义类加载器,会先初始化父类ClassLoader,
            // 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
            MyClassLoader1 classLoader1 = new MyClassLoader1("E:/jvm");
            Class clazz = classLoader1.loadClass("com.huigu.stu.Student");
            Object obj = clazz.newInstance();

            Method method = clazz.getDeclaredMethod("study",null);
            method.invoke(obj,null);

            System.out.println(clazz.getClassLoader().getClass().getName());

        }


        //重写类加载方法,实现自己的加载逻辑,不为派给双亲加载


        @Override
        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(!name.startsWith("com.huigu.stu")){
                    c = this.getParent().loadClass(name);
                   // c = super.loadClass(name,resolve);
                }else{
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
                    }
                    if (resolve) {
                        resolveClass(c);
                    }
                }
                return c;
            }
        }
    }


}

public class Student {
    private String name;
    public void study(){
        System.out.println("好好学习,天天向上");
    }
}

运行结果:

   好好学习,天天向上
   test01.MyClassLoader$MyClassLoader1

那么,如果打破双亲委派机制,用自定义类加载器加载自己写的java.lang.Math.class 呢?请看下面代码:

import java.io.FileInputStream;
import java.lang.reflect.Method;

/**
 * Created 
 */
public class MyClassLoader {
    static class MyClassLoader1 extends ClassLoader{
        private String classPath;

        public MyClassLoader1(String classPath){
            this.classPath = classPath;

        }

        private byte[] loadByte(String name)throws Exception{
            name = name.replaceAll("\\.","/");
            FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        @Override
        protected Class findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name,data,0,data.length);
            }catch (Exception e){
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
        public static void main(String[] arg) throws Exception{
            //初始化自定义类加载器,会先初始化父类ClassLoader,
            // 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
            MyClassLoader1 classLoader1 = new MyClassLoader1("E:/jvm");
            Class clazz = classLoader1.loadClass("java.lang.Math");
            Object obj = clazz.newInstance();

            Method method = clazz.getDeclaredMethod("study",null);
            method.invoke(obj,null);

            System.out.println(clazz.getClassLoader().getClass().getName());

        }


        //重写类加载方法,实现自己的加载逻辑,不为派给双亲加载


        @Override
        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(!name.startsWith("java.lang.Math")){
                    c = this.getParent().loadClass(name);
                   // c = super.loadClass(name,resolve);
                }else{
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
                    }
                    if (resolve) {
                        resolveClass(c);
                    }
                }
                return c;
            }
        }
    }


}

//自定义的Math类
package java.lang;

/**
 * Created
 */
public class Math {
 public static void main(String[] args) {
     System.out.println("**************进入Math类**************");
 }
}

 运行结果:

        java.lang.SecurityException: Prohibited package name: java.lang
        at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
        at test01.MyClassLoader$MyClassLoader1.findClass(MyClassLoader.java:32)
        at test01.MyClassLoader$MyClassLoader1.loadClass(MyClassLoader.java:69)
       安全类型异常,禁止加载java.lang包中的类,出于安全性考虑,jvm是不允许自定义类加载器加载像java.lang这种核心包中的类的。

本篇博客就写到这里了,感谢您的观阅,如果对您有所帮助,希望给个关注,错误之处,欢迎指正,如有疑问,欢迎来询。下篇是tomcat打破双亲委派机制,期待再次相见。

参考
 
《深入理解Java虚拟机》

你可能感兴趣的:(JVM类加载机制源码分析,java,jvm,jdk)