JVM系列一:从类加载器、全盘委派机制、双亲委派机制开始认识jvm

前言

  • 最近在做知识总结,在总结在jvm这一块时,发现脑海中对jvm的知识点并没有清晰,不能张口就说。于是想通过对jvm系列的知识总结,为jvm添加一个hash类型的索引(哈哈哈,不小心又张口说了一个知识点:hash类型的索引适用于等值查询)。废话不多说,直接切入正题。

一、类加载器类型及功能

  • 在学习java时,我相信绝大多数人都写过下面一个类Test.java

    public class Test {	
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    

    并且配置java环境变量,使用javac Test.javajava Test来运行这个java文件。咱们今天研究这整个过程是怎么完成的,咱们先来提一个问题:System.out.println()这个api是从哪里来的? 问题提完了,不知道看到这的你是否已经有答案?如果还没有答案的话,接着往下看。

  • 我们可以确定System.out.println()这肯定是调用了System.java类内部的叫out的属性的println方法。经过一番查找,发现System.java是位于java.lang包下的类。我们使用idea定位下System.java类所处的位置:
    在这里插入图片描述
    注意:这仅仅是在idea中查看jdk的位置。并不是java程序运行时会用到src.zip压缩包的rt.jar包。这里要先声明下jdk和jre的关系:当我们要开发java程序时是要安装jdk的,但是如果我们想运行java程序,只需要安装jre就行了
    到这,我们能确定System.java类就是安装jdk时自带的。那它到底是怎么个加载到这个类的呢?我们先来认识下类加载器或许就能立刻明白了。

  • 各种类加载器类型即总结如下表所示:

    类加载器类型作用获取方式备注
    系统加载器(AppClassLoader)加载当前应用classpath下的class文件至jvm内存中ClassLoader appClassLoader = ClassLoader.getSystemClassLoader()线程上下文获取的类加载器就是系统加载器
    扩展加载器(ExtClassLoader)加载%JAVA_HOME%/jre/lib/ext路径下的所有jar包ClassLoader extClassLoader = appClassLoader.getParent()
    根加载器(BootstrapClassLoader)加载%JAVA_HOME%/jre/lib路径下的所有jar包ClassLoader rootClassLoader = extClassLoader.getParent()在java中获取的为null, 因为是由C++写的

    咱们先看一下根加载器的作用:加载%JAVA_HOME%/bin/jre/lib路径下的所有jar包%JAVA_HOME%就是配置的JAVA的环境变量,eg:我的JAVA_HOME的值为:D:\jdk\jdk1.8(我的jdk就安装到此目录下)。咱们来看一下D:/jdk/jdk1.8/jre/lib目录下长什么样在这里插入图片描述
    结合上面第一张图中System.java类所处的位置(位于rt.jar包中)。于是我们能得出这么一个结论:根加载器是将java中的核心类给加载到classpath中去了,比如java.lang包。由根加载器的一个工作流程,我们可以了解到类加载器就是将一些特定目录下的jar包加载到classpath下,在执行javac命令时,就能找到对应的类所处的位置,进而编译成功。

二、全盘委派机制

  • 如何将一个java文件加载到jvm内存?大致能想到的就是使用jdk自带的api:Class.forName("全限定名")。但是使用这个api有一个特点,就是内部有这么一段代码:
    	public static Class<?> forName(String className)
                    throws ClassNotFoundException {
        // 拿到调用方的Class类,假设在类A的main方法中调用了Class.forName("com.xxx.xxx");
        // 那么获取到的调用方的Class类就是类A  <==>  caller就是A的class对象。
        // 最终会使用类A的类加载器将com.xxx.xxx类加载到jvm中 <==> ClassLoader.getClassLoader(caller)
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
    
    由上述代码可知,在类A调用了Class.forName这个api,用的就是类A的类加载器来加载类。这就是全盘委派机制

三、双亲委派机制

  • 双亲委派机制我画了一幅图来形容, 很直观:
    在这里插入图片描述
  • 双亲委派机制有一个规则:每一个类加载器只能做自己的工作,假设我们想让BootstrapClassLoader来加载classpath路径下的某个类,这是行不通的(正常情况下)

四、如何破坏双亲委派机制之jdbc驱动破坏双亲委派机制原理

  • 正常情况下(未破坏双亲委派机制)的代码编写:

    Class.forName("com.mysql.jdbc.Driver");
    Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mysqlDB?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true", "root", "");
    System.out.println(connection);
    

    解析(结合源码来看总结会比较清楚):

    上面有说到,全局委派机制。因为在我们自定义写的类A中调用了Class.forName(“com.mysql.jdbc.Driver”);方法,
    所以com.mysql.jdbc.Driver的加载是由AppClassLoader完成的。而且在执行Class.forName(“com.mysql.jdbc.Driver”)代码时,使用的是扩展类加载器(因为jdbc是第三方jar包)把它加载到jvm中去的,在加载的过程中,调用了com.mysql.jdbc.Driver类的静态代码块。静态代码块的主要逻辑是把mysql的驱动添加到DriverManager的registeredDrivers属性中去了。最后在DriverManager.getConnection方法需要使用到这个驱动类时是直接从registeredDrivers属性中拿驱动获取连接的。

  • 破坏双亲委派的情况:

    Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mysqlDB?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true", "root", "");
    System.out.println(connection);
    

    解析:

    可以看到,我们并没有手动将com.mysql.jdbc.Driver类加载到jvm中去。而DriverManager是java.sql包下的,位于rt.jar包。所以DriverManager肯定是由根加载器加载到jvm的。而在加载DriverManager类时,内部的静态代码会使用java的spi技术。读取classpath下META-INF/services/java.sql.Driver文件,里面存储的就是一些实现了java.sql.Driver接口的实现类。其中就包括com.mysql.jdbc.Driver。我们拿到的还只是字符串,我们要把它加载到jvm中,会用到Class.forName()的api。由上述的全盘委托机制可知,在DriverManager内部执行Class.forName()方法,最终肯定使用的是根加载器。但是com.mysql.jdbc.Driver并不在%JAVA_HOME%/jre/lib路径下,所以肯定是不能加载到的。此时就用到了ClassLoader cl = Thread.currentThread().getContextClassLoader()代码来获取当前线程的类加载器, 此段代码获取的是系统加载器,即AppClassLoader,然后使用Class.forName的另外一个能指定类加载器的api(java.lang.Class#forName(java.lang.String, boolean, java.lang.ClassLoader))去加载类。所以最终是使用系统加载器来加载com.mysql.jdbc.Driver类

五、总结

  • 通过本次总结,了解了类加载器的类别、全盘委派机制、双亲委派机制的含义即原理。下篇文章将围绕jvm内存结构进行总结。
  • Github地址:传送门
  • I am a slow walker, but I never walk backwards.
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值