zoukankan      html  css  js  c++  java
  • JVM类加载过程详细分析

    双亲委派加载模型

    为什么需要双亲委派加载模型

    主要是为了安全,避免用户恶意加载破坏JVM正常运行的字节码文件,比如说加载一个自己写的java.util.HashMap.class。这样就有可能造成包冲突问题。

    类加载器种类

    file

    • 启动类加载器:用于加载jdkrt.jar的字节码文件
    • 扩展类加载器:用于加载jdk/jre/lib/ext文件夹下的字节码文件
    • 应用程序类加载器:加载classPath下的字节码文件
    • 自定义类加载器:用户在程序中自己定义的加载器

    源码分析

    1、ClassLoader.loadClass()

        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);
                // 如果这个Class对象还没有被加载,下面就准备加载
                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
                    }
    				// 如果父类加载器也没有加载这个Class对象,就由自己来加载
                    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;
            }
        }
    

    不遵守双亲委派加载模型的例子

    双亲委派加载模型仅仅是一个约定,后面实现类加载器时,是可以不遵守这个约定。ClassLoader是在JDK1.0的时候就设计好的,而双亲委派加载模型在JDK1.2引入的。所以,有些机制是没有遵守这个约定的。比如:Service Provider Interface机制的JDBC就没有遵守这个约定。

    1、为什么JDBC无法遵守这个约定?
    JDBCSPI机制的一个例子,JDK定义了java.sql.Connection核心接口,后续MySQLOracle为其提供实现类。在运行中是通过java.sql.DriverManager来获取指定实现类的实例。这里需要明白三个问题:

    • java.sql.DriverManager是在rt.jar中,由核心类加载器加载的;
    • 第三方所提供Collection的实现类都是在classpath中;
    • 类中方法想加载新的字节码文件时,其初始类加载器就是当前这个类的定义类加载器;

    也就是说当JVMjava.sql.DriverManager类的getConnection()方法中获取Collection实现类的字节码时,当前类的定义类加载器是启动类加载器,而按照约定启动类加载器是不允许加载classpath下的字节码。所以,JDBC就无法遵守这个约定。

    2、JDBC是如何解决上面的问题的?
    为了解决这个,java在线程中放入一个类加载器Thread.currentThread().getContextClassLoader();而这个类加载器可以是随意的。比如你想加载classpath包下的字节码文件,只需要设置当前线程的类加载器为应用程序类加载器即可。

    JVM类加载过程

    JVM本质的工作就是读取字节码文件、执行字节码文件中的指令。其中JVM将读取字节码文件的过程称为JVM类加载过程。

    JVM读取的字节码文件将放在方法区里;

    JVM类加载机制分为五个部分:加载、验证、准备、解析、初始化。如下图所示:
    file

    一、Loading:加载

    这一步是将JVM外的字节码文件加载到JVM内部方法区中的Class对象。

    JVM可以通过几种方式来加载外部的字节码文件?

    • 从本地读字节码文件;
    • 从网络读取字节码文件;
    • 通过动态生成的字节码文件;

    初始类加载器和定义类加载器

    由于双亲委派加载模型的存在,一个Class对象的初始类加载器initiating class loader和定义类加载器defining class loader有可能不是同一个。

    • 初始类加载器:它是指让JVM加载这个字节码文件
    • 定义类加载器:它是真正调用defineClass方法,将字节码转换成Class对象

    java在判断instanceof时,只有类名、defining class loader都相等,才表示是同一个类的实例。

    Class.getClassLoader()得到的是定义类加载器

    相关实验代码

    1、验证使用不同ClassLoader加载字节码文件

    // 这种方法是不遵守双亲委派加载模型的约定
    public class ClassLoaderLoading {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            // 这个Class对象是由当前方法的类加载器加载
            Class c1 = MiniJVM.class;
            Class c2 = new MyClassLoader().loadClass("com.github.hcsp.MiniJVM");
            // 使用c2创建一个MiniJVM实例
            Object o = c2.getConstructor().newInstance();
            System.out.println(o instanceof MiniJVM);
            MiniJVM demo = (MiniJVM) o;
        }
    
        private static class MyClassLoader extends ClassLoader {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                if (name.contains("MiniJVM")) {
                    try {
                        byte[] bytes = Files.readAllBytes(new File("target/classes/com/github/hcsp/MiniJVM.class").toPath());
                        return defineClass(name, bytes, 0, bytes.length);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    return super.loadClass(name);
                }
            }
        }
    }
    

    2、实现一个遵守双亲委派加载模型的类加载器

    public class ClassLoaderLoading {
        public static void main(String[] args) throws ClassNotFoundException {
            Class c1 = MiniJVM.class;
            Class c2 = new MyClassLoader(ClassLoader.getSystemClassLoader()).loadClass("com.github.hcsp.MiniJVM");
            System.out.println("c2 = " + c2);
        }
    
        private static class MyClassLoader extends ClassLoader {
            public MyClassLoader(ClassLoader systemClassLoader) {
                super(systemClassLoader);
            }
            
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                // 加载你想让这个类加载器加载的字节码文件
                if (name.contains("MiniJVM")) {
                    try {
                        byte[] bytes = Files.readAllBytes(new File("target/classes/com/github/hcsp/MiniJVM.class").toPath());
                        return defineClass(name, bytes, 0, bytes.length);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    // 其他的字节码文件交由父类加载器加载
                    return super.loadClass(name);
                }
            }
        }
    }
    

    二、Linking:链接

    当一个.java文件编译成.class文件时,里面含有一个符号引用,比如/java/utils/HashMapLinking是指将这符号引用与具体的class对象链接起来。

    每个字节码结构都有一个运行时常量池,它会存储每个符号引用和所对应的具体对象,以此实现链接。

    • Verification:验证字节码的正确性
    • Preparation:为static成员赋默认初始值
    • Resolution:解析当前字节码里包含的其他符号引用

    三、Initializing

    执行初始化方法。比如下面的四个虚拟机指令:newgetstaticputstaticinvokestatic

    原博客地址

  • 相关阅读:
    Git

    学而不记则徒劳无功
    Redis基础
    哈希表
    第一个Python程序
    Python 环境搭建 基于 Windows
    执行数据库的插入操作 insert
    Eclipse连接到My sql数据库的操作总结/配置数据库驱动
    数据库 (一)
  • 原文地址:https://www.cnblogs.com/fourther/p/12687964.html
Copyright © 2011-2022 走看看