• 类加载机制深度解析


    一、类加载过程

    多个java文件经过编译打包生成可运行jar包,最终由java命令运行某个主类的main启动程序,这里需要先通过类加载器把主类加载到JVM
    主类在运行过程中如果使用到其他类,会逐步加载这些类。
    注意:jar包里的类不是一次性全部加载的,是使用到时才加载,不过类似于java.lang.Object这种支持JVM运行的类会在启动时便被加载。
    类加载过程
    加载>>验证>>准备>>解析>>初始化>>使用>>卸载
    • 加载:在硬盘上查找并通过IO将字节码文件读入到内存中,使用到类时才会被加载,例如调用类的main()方法,new对象等
    • 验证:验证字节码文件的正确性
    • 准备:为类的静态变量分配内存空间,并给静态变量赋予初始值
    • 解析:将符号引用替换为这直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链 接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用 
    • 初始化:对类的静态变量初始化为指定的值,执行静态代码块
    二、类加载器和双亲委派机制
    类的加载主要通过类加载器来实现,java中的类加载器如下:
    • 启动类加载器(BootstrapClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
    • 扩展类加载器(ExtClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
    • 应用程序加载器(AppClassLoader):负责加载ClassPath路径下的类包,主要是java开发人员自己写的类生成的字节码文件
    • 自定义加载器:开发人员可以通过继承ClassLoader这个类来自定义自己的类加载器,只需要实现findClass()这个方法即可,如果要打破双亲委派机制则需要额外自己实现loadClass()这个方法修改代码使其不使用双亲委派的方式。
    类加载器示例:
    package jvm;
    
    public class TestJDKClassLoader {
        public static void main(String[] args) {
            System.out.println(String.class.getClassLoader());
            System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
            System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
            System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
        }
      
    结果:
    null//启动类加载器是使用C++语言实现,所以无法打印
    sun.misc.Launcher$ExtClassLoader
    sun.misc.Launcher$AppClassLoader
    sun.misc.Launcher$AppClassLoader
     
    双亲委派机制的逻辑大致如下:
    1.首先加载指定名称的类是否已被加载过,如果加载过就不需要重复加载,直接返回。
    2.如果此类没有被加载,那么判断是否有父类加载器,如果有,则委派给父加载器加载,如果没有则直接委派给启动类加载器加载。
    3.如果父加载器及bootstrapClassLoader均没有找到目标类则有当前类加载器的findClass完成加载。
    总结:加载器加载时将加载动作逐级向上委托直到最高级的启动类加载器,再从最高级向下逐级进行目标类加载,如果在某一级加载到了目标类则不再向下继续。
    设计双亲委派机制的目的:
    • 沙盒安全机制:java开发人员自己写的java.lang.class不会被加载,防止核心API库被篡改。
    • 避免类的重复加载:当父亲类加载器已经加载到目标类时,字加载器便不会在进行加载,保证的被加载类的唯一性。
    在自定义类加载器示例:
    自定义类加载器主要是重写findclass()方法:
    package jvm;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    
    //自定义类加载器
    public class MyClassloaderTest extends ClassLoader{
        private String classPath;
    
        //初始化时指定字节码目录所在目录
        public MyClassloaderTest(String classPath) {
            this.classPath = classPath;
        }
    	//将字节码文件加载到内存中
        private byte[] loadByte(String name) throws IOException {
            name = name.replaceAll("\.","/");
            FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
            int len = fis.available();
            byte[] bt = new byte[len];
            fis.read(bt);
            fis.close();
            return bt;
        }
    
        //通过该类返回Class对象
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name,data,0,data.length);
            } catch (IOException e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
    }
    
     
     
    打破双亲委派机制
    如果要打破双亲委派机制只需要重写loadClass()这个方法
    package jvm;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    
    //自定义类加载器
    public class MyClassloaderTest extends ClassLoader{
        private String classPath;
    
        public MyClassloaderTest(String classPath) {
            this.classPath = classPath;
        }
    
        private byte[] loadByte(String name) throws IOException {
            name = name.replaceAll("\.","/");
            FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
            int len = fis.available();
            byte[] bt = new byte[len];
            fis.read(bt);
            fis.close();
            return bt;
        }
    
        @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 (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;
            }
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name,data,0,data.length);
            } catch (IOException e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
    }
    
     
    测试类:
     package jvm;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class Test {
        public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
            MyClassloaderTest classloader = new MyClassloaderTest("F:\test");
            Class clazz = classloader.loadClass("java.lang.String");
            Object o = clazz.newInstance();
            Method method = clazz.getDeclaredMethod("sout", null);
            method.invoke(o,null);
            System.out.println(clazz.getClassLoader().getClass().getName());
        }
    }
    
     



  • 相关阅读:
    自定义动画(仿Win10加载动画)
    jquery 仿windows10菜单效果下载
    mac os+selenium2+Firefox驱动+python3
    mac os+selenium2+chrome驱动+python3
    mac 安装scrapy
    mac 查看系统位数
    ubuntu安装 mysqldb
    ubuntu安装常用软件
    scp 时出现permission denied
    ubuntu 安装git
  • 原文地址:https://www.cnblogs.com/qishanmozi/p/c332d9e7c0c72831e4a4b0ae9f19e8f4.html
Copyright © 2020-2023  润新知