【心得分享】-- 类的加载机制深度解析

1. 类的加载机制深度解析

1.1 类的加载运行过程

【心得分享】-- 类的加载机制深度解析_第1张图片

如上图所示,Java中的类的加载靠的是类加载器实现的,其中通过loadClass进行类加载的时候会经历:加载》》验证》》准备》》解析》》初始化几个步骤。

  • 加载:查找磁盘中的类的字节码文件并通过io操作读入,只有类被真正使用的时候才会被加载
  • 验证:验证字节码是否符合规范
  • 准备:为静态变量分配内存空间,并且赋上默认的初始值。int类型赋值0、boolean类型赋值false、对象类型赋值null
  • 解析:将符号引用(静态方法)转换为直接引用
  • 初始化:为静态变量赋上真正的值

【心得分享】-- 类的加载机制深度解析_第2张图片

注意:主类在运行时如果使用到其他类,这些类会被逐步加载,jar包和war包中的类并不是一次性都加载出来的,而是真正使用到这个类的时候才会进行加载

package com.muzili.jvm;

public class TestDynamicLoad {
     

    public static String initDate="test";

    static {
     
        System.out.println("运行TestDynamicLoad类的静态方法.....");
    }

    public TestDynamicLoad(){
     
        System.out.println("运行TestDynamicLoad类的构造方法.....");
    }


    public static class InnerClassA{
     
        static {
     
            System.out.println("运行InnerClassA类的静态方法.....");
        }

        public InnerClassA(){
     
            System.out.println("运行InnerClassA类的构造方法.....");
        }
    }

    public static void main(String[] args) {
     
        TestDynamicLoad testDynamicLoad=new TestDynamicLoad();

        InnerClassA innerClassA=new InnerClassA();
    }
}

结果输出:
运行TestDynamicLoad类的静态方法.....
运行TestDynamicLoad类的构造方法.....
运行InnerClassA类的静态方法.....
运行InnerClassA类的构造方法.....

Process finished with exit code 0

1.2 类加载器和双亲委派机制

1.2.1 类加载器

Java中的类加载器主要有三种:BootstrapClassLoader(引导类加载器)ExtClassLoader(扩展类加载器)AppClassLoader(应用程序类加载器)

  • BootstrapClassLoader:主要用于加载Java程序运行时必不可少的核心类,如jre/lib/rt.jarjre/lib/charsets.jar中的类。
  • ExtClassLoader:用于加载jre/lib/ext下的所有jar包中的类
  • AppClassLoader:用于加载自己编写的类
    【心得分享】-- 类的加载机制深度解析_第3张图片
package com.muzili.jvm;

import com.sun.crypto.provider.DESKeyFactory;
import sun.misc.Launcher;

import java.net.URL;

public class TestJDKClassLoader {
     

    public static void main(String[] args) {
     
        /**
         * 输出以下类的ClassLoader
         */
        System.out.println(String.class.getClassLoader());
        System.out.println(DESKeyFactory.class.getClassLoader());
        System.out.println(TestJDKClassLoader.class.getClassLoader());

        System.out.println();

        /**
         * 获取ClassLoader
         */
        //因为BootStrapClassLoader是通过C++实现,并实例化的故Java程序无法获取
        ClassLoader appClassLoader=TestJDKClassLoader.class.getClassLoader();
        ClassLoader extClassLoader=appClassLoader.getParent();
        ClassLoader bootstrapClassLoader=extClassLoader.getParent();

        System.out.println("the BootStrapClassLoader:"+bootstrapClassLoader);
        System.out.println("the ExtClassLoader:"+extClassLoader);
        System.out.println("the AppClassLoader:"+appClassLoader);

        System.out.println();

        URL[] bootStrapUrls = Launcher.getBootstrapClassPath().getURLs();

        System.out.println("BootStrapLoader加载:");

        for (URL url : bootStrapUrls) {
     
            System.out.println(url.getPath());
        }

        System.out.println();

        System.out.println("ExtClassLoader加载:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();

        System.out.println("AppClassLoader加载:");
        System.out.println(System.getProperty("java.class.path"));
    }

}

结果输出:
null
sun.misc.Launcher$ExtClassLoader@4b67cf4d
sun.misc.Launcher$AppClassLoader@18b4aac2

the BootStrapClassLoader:null
the ExtClassLoader:sun.misc.Launcher$ExtClassLoader@4b67cf4d
the AppClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2

BootStrapLoader加载:
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/resources.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/rt.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/sunrsasign.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jsse.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jce.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/charsets.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jfr.jar
/E:/Program%20Files/Java/jdk1.8.0_191/jre/classes

ExtClassLoader加载:
E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

AppClassLoader加载:
E:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;D:\workspace\tulingxueyuan-demo\out\production\jvm-demo-01;E:\Program Files\JetBrains\IntelliJ IDEA 2019.1.1\lib\idea_rt.jar

Process finished with exit code 0

类加载过程解析:

  1. 通过jvm创建BootstrapClassLoader实例,调用sun.misc.Launcher#getLauncher()方法获取启动器对象【这里使用到了设计模式中的单例模式】
  2. Launcher类的构造方法中,创建了两个类加载器:sun.misc.Launcher.ExtClassLoader#getExtClassLoader[扩展类加载器]、sun.misc.Launcher.AppClassLoader#getAppClassLoader[应用程序类加载器]
  3. JVM默认会使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader用于加载我们的类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package sun.misc;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.nio.file.Paths;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.HashSet;
import java.util.StringTokenizer;
import java.util.Vector;
import sun.net.www.ParseUtil;

public class Launcher {
     
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    public static Launcher getLauncher() {
     
        return 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);
        }

    }

    public ClassLoader getClassLoader() {
     
        return this.loader;
    }
    
    //省略一些其他代码
}

1.2.2 双亲委派机制

【心得分享】-- 类的加载机制深度解析_第4张图片

双亲委派机制,就是在加载类的时候,级别较低的类加载器首先会委托它的父级类加载器进行加载,如果父级类加载器加载不到才会交给当前类加载器进行加载。

双亲委派机制有什么意义:

  1. 沙箱安全:它可以保证java中的核心类不会被用户定义的类给覆盖,从而保证了系统的安全
  2. 防止类的重复加载:每个类加载器,都实现了缓存机制,当类第一次被加载之后就会被缓存起来,当再次加载的时候,就不用再每次都按照双亲委派机制来加载类,这样可以提升类的加载效率

1.3 自定义类加载器

为什么需要自定义类加载器?

如果在实际开发中,可能有时我们想要加载一些外部目录下的类,这个时候jvm提供的类加载器就无法满足我们的需求了,这个时候我们需要自定义类加载器,用以加载外部目录中的类。

1.3.1 定义一个简单的类加载器

package com.muzili.jvm;

import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoaderTest {
     

    public static class MyClassLoader extends ClassLoader{
     

        private String clasasPath;

        public MyClassLoader(String clasasPath) {
     
            this.clasasPath = clasasPath;
        }

        private byte[] loadByte(String name) throws IOException {
     
            name = name.replace(".", "/");

            FileInputStream fis=new FileInputStream(clasasPath+"/"+name+".class");

            int len=fis.available();

            byte[] bytes=new byte[len];

            fis.read(bytes);

            fis.close();

            return bytes;
        }


        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
     

            try {
     
                byte[] bytes = this.loadByte(name);
                return defineClass(name,bytes,0,bytes.length);
            } catch (IOException e) {
     
                e.printStackTrace();
            }

            return null;
        }
    }


    public static void main(String[] args) {
     
        //初始化自定义的类加载器
        MyClassLoader myClassLoader=new MyClassLoader("D:\\test");

        try {
     
            Class<?> clazz = myClassLoader.loadClass("com.muzili.jvm.User1");

            System.out.println(clazz.getClassLoader());
        } catch (ClassNotFoundException e) {
     
            e.printStackTrace();
        }
    }

}

运行结果:
com.muzili.jvm.MyClassLoaderTest$MyClassLoader@4554617c

Process finished with exit code 0

1.3.2 打破类加载器的双亲委派机制

package com.muzili.jvm;

import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoaderTest2 {
     

    public static class MyClassLoader extends ClassLoader{
     

        private String clasasPath;

        public MyClassLoader(String clasasPath) {
     
            this.clasasPath = clasasPath;
        }

        private byte[] loadByte(String name) throws IOException {
     
            name = name.replace(".", "/");

            FileInputStream fis=new FileInputStream(clasasPath+"/"+name+".class");

            int len=fis.available();

            byte[] bytes=new byte[len];

            fis.read(bytes);

            fis.close();

            return bytes;
        }


        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
     

            try {
     
                byte[] bytes = this.loadByte(name);
                return defineClass(name,bytes,0,bytes.length);
            } catch (IOException e) {
     
                e.printStackTrace();
            }

            return null;
        }

        /**
         * 打破双亲委派机制
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        public 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();

                    //直接将双亲委派机制取消不做任何处理会导致java运行时非常重要的类无法加载
                    if (!name.startsWith("com.muzili.jvm")){
     
                        c=this.getParent().loadClass(name);
                    }

                    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;
            }
        }
    }


    public static void main(String[] args) {
     
        //初始化自定义的类加载器
        MyClassLoader myClassLoader=new MyClassLoader("D:\\test");

        try {
     
            Class<?> clazz = myClassLoader.loadClass("com.muzili.jvm.User1");

            System.out.println(clazz);
        } catch (ClassNotFoundException e) {
     
            e.printStackTrace();
        }
    }

}

结果输出:
class com.muzili.jvm.User1

你可能感兴趣的:(心得分享,JVM)