专栏首页程序员的成长之路JVM的特性,通过代码来揭秘类加载器

JVM的特性,通过代码来揭秘类加载器

技术文章第一时间送达!

类加载器

首先,我们先来看一个简单的程序。

/**
 * 作者:LKP
 * 时间:2018/11/7
 */public class Test {    public static void main(String[] args) {
        System.out.println("liaokangping");
    }

}一个简单的打印输出,虽然这个程序只打印了这样一句简单的话,但是呢,它也要经过编译器,编译成字节文件,才能执行。

什么是字节文件呢?

这个Test.class文件,就是我们刚刚执行程序,运行编译之后的字节文件。

看看它的内容:

是不是一脸懵逼,我也看不懂,因为这是给机器读取的。

先上一张图,仔细看清楚哦~~~

这是一个类加载的流程,前面所说的过程,就是编译器将Test.java文件编译为了Test.class文件。

编译成字节文件之后,这时就是类加载器闪亮登场的时候了。

何为类加载器(ClassLoader)?

可能你已经知道了,不过这里我就先卖个关子,欲知何为类加载器,请看后续内容...

手机上的王者荣耀,吃鸡游戏.....,相信大家都玩吧,它们在运行的时候,是不是要把相关的文件加载到手机内存里面。但是有个前提,是不是只有我们点击游戏启动的时候,它才会进行加载呢。

程序是通过什么来触发这个加载的呢?

我想你已经猜到了,就是通过run来触发。

到这里又有个小问题了,我们通过run启动之后,它执行了几个动作呢?

首先,在编译器里面,将.java的文件编译为.class文件,再通过类加载器ClassLoader加载到内存里面(运行时数据区),之后通过执行器调用被本地方法接口,再去调用本地方法库,最后打印出结果:

这就是类加载的一个流程,就是前面图中的整个执行流程。

到这里,你也一定猜到类加载器是做什么的吧。

没错,类加载器就是把字节码文件加载到运行时数据区里面的一个机制。但是呢,整个机制不是这么简单的,它在Java类里面也有相关的类。

前面一直有出现ClassLoader,我们现在来看看这个ClassLoader到底是个什么东西。

改造一下刚刚的程序:

/**
 * 作者:LKP
 * 时间:2018/11/7
 */public class Test {    public static void main(String[] args) {
        ClassLoader c = Test.class.getClassLoader();
        System.out.println(c);
    }

}好吧,实际上已经重写了。

看一下运行结果:

打印出来了这个东西,这又是什么呢,还是不明白类加载器到底执行了什么过程啊?

不着急,继续往下看。

我们用一条指令,看一下类加载器的过程中到底有些什么过程,我们借助一下JDK的一些工具去分析它。

Java -verbose

我们用这个指令来看一下(PS:移动到该项目存放class文件的根目录,下个目录就是包名的地方)

执行之后,就可以看到这样的信息了,会打印输出四百多行的信息,这里就不贴出来全部了。(PS:你的cmd窗口可能只有两百多,这个时候需要设置一下,右击边框>属性>布局>屏幕缓冲区大小>把高度值设置大一些)

为了方便阅读,我们把它拷贝到其他地方。

我们先来看一下第一行打印出来的信息

Opened,打开,意思是打开一个jar包。

再看一下第二行,是不是很眼熟

Loaded 加载 第一个就是加载的Object类,Object就不用多说了吧,它就是所有类的基类,也可以说是老祖宗。

不难发现,在整个加载过程中,只有rt.jar这个jar包,没有其他的jar包

我们再来看到最后

这是啥?这是我们刚刚打印的那段话,还记不记得。

我们在全文搜索一下,从加载过程的最前面开始搜索

先来解析一下,$符的意思是表示内部类,谁的内部类呢,看一下它前一行代码,Launcher里面有两个内部类。

一个内部类是AppClassLoader,另外一个内部类就是ExtClassLoader。

我们通过程序来理解一下ClassLoader它们之间的关系,改造一下刚刚的程序:

/**
 * 作者:LKP
 * 时间:2018/11/7
 */public class Test {    public static void main(String[] args) {
        ClassLoader c = Test.class.getClassLoader();        while(c != null){
            System.out.println(c);
            c = c.getParent();
        }
    }

}执行结果:

跟我们刚刚打印的是一样的,那么它们之间的关系是什么呢?

ClassLoader c = Test.class.getClassLoader(); 我们通过这行代码拿到Test这个类的加载器,这里问题又来了,我们要用什么把它加载到类加载器里面去呢?

用什么加载,看完下面的内容,你就知道了。

看到AppClassLoader,我们要找到类加载器的父级,也可以是找到它爹。

这里打印了两行信息,一个是AppClassLoader,一个是ExtClassLoader,可以知道,AppClassLoader的爹就是ExtClassLoader。ExtClassLoader的爹又是谁呢?我们要去找找。

先打开ClassLoader这个类的源码,找到loadClass方法。

看一下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);            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.
                    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;
        }
    }这是它的源码。

首先,我们先来看一段代码

它是JDK里面类加载最经典的地方,掌握了这个地方,面试的时候,关于百分之九十的类加载器你就已经明白了。

解析一下loadClass方法,直接上图了

去看一下 findBootstrapClassOrNull(name); 这个方法

知道native的含义吗?

native就是本地方法,一般本地方法用的是c/c++写的,直接就跟我们前面的那张图本地方法库有关系

为什么说java可以一次编译,到处运行呢?

它就是我们的一个c/c++写的一个linux库,在windows平台里面就是windows库,本地接口方法调用本地方法库。

还有本地方法跟操作系统有关系,因为linxu和windows操作系统加载文件的机制不一样的。

其实,到这里已经追踪到它的源头了,也就是找到它的老祖宗:BootstrapClassLoader

这里有三层关系,第一层是AppClassLoader,第二层是ExtClassLoader,第三层是BootstrapClassLoader,那么他们每层都负责加载什么东西呢?

我们回顾一下加载类时打印出来的信息

首先是打开一个jar包,和文件系统就有关系,那么肯定是本地方法。

所以BootstrapClassLoader是打开本地的rt.jar包

那么这两个又是调用什么东西的呢?看看下图。

说明的一下,他们绝对不是继承关系,只是一个组合关系!!!切记

前面还留了一个疑问,什么加载器把Test这个类加载呢?答案是:AppClassLoader应用程序类加载器。

来看一下自定义类加载器:

tomcat里面也有类加载器的,在lib路径里的catalina.jar包。

加载关系:

tomcat就属于自定义类加载器。

回到之前的程序,继续阅读loadClass方法。

可以得出结论,检查顺序是自底向上,加载顺序是自顶向下

这种模式叫双亲委派或者叫双亲委任。

为什么要用双亲委派呢?它的好处在什么地方。

我们用代码来理解,接下来我们来新建个类,在之前先创建个包

这个包大家应该很熟悉了。

我们在自己创建的java.util包下面创建个List类

/**
 * 作者:LKP
 * 时间:2018/11/7
 */public class List {    public static void main(String[] args) {
        System.out.println("我是List");
    }
    
}思考两个问题:

大家觉得这个类能被编译吗?

这个List类能执行吗?

我们来尝试一下,运行之后,去找一下它的编译文件。

看到这,清楚看到,它是可以成功编译的

看看运行结果是什么?

它是不能运行的!

关于双亲委派

为什么是安全机制呢,来看一下代码。

排他锁。在我加载的时候,排除其他程序加载这个类

从这个里面拿出来看一下是否已经被加载了

这两行代码注定了这个类只能被加载一次。

双亲委派保证了父类能加载的就不给子类加载。

这里我再去看一下之前类加载过程的打印信息。

这个List已经被加载过了,所以它不会再给子类加载了,这就是双亲委派这种安全模型。

程序安全是JDK的事,加入黑客随便写了个List,把我这个List篡改了,不好意思,安全程序是由JDK控制的。

不过也不是没办法,它侵入你的服务器,把JDK的List删除,再把自己写的放进入,这就没办法了,不过这是服务器的漏洞。

可以换句话,就是程序员写了不安全的代码,JDK有责任不让它运行。

看下Java类的生命周期:

链接的过程还分为:验证,准备,解析三个部分。

验证阶段:

1.7的东西拿到1.6上去就会报 50.0,1.8拿到1.7上去运行,就会包51.0

这是验证java的运行版本。

我们再来验证一个东西。

把这段代码,转换一下。

转换后:

我们可以把所有的类都拿出来,看一下前面是不是就有这个。

这个叫神马呢?

这是:MagicNumber 魔数

这相当于文件头信息,检查就是这个头信息。

再放一张完整的图,类加载的知识就到这里了。

这是自己学习的一些记录,方便以后回顾,有错误的地方欢迎留言

本文分享自微信公众号 - 程序员的成长之路(cxydczzl),作者:良月柒

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-11-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【原创】JVM 的类加载机制?盘它!

    如上图所示,假设写一个类A存储为A.java,通过javac A.java编译生成A.class,A.class中存储了各种描述A类的信息。

    良月柒
  • Java反射-高级开发必须懂的

    理解反射对学习Java框架有很大的帮助,如Spring框架的核心就是使用Java反射实现的,而且对做一些Java底层的操作会很有帮助。

    良月柒
  • 19 个强大、有趣、好玩、又装B的 Linux 命令!

    输出一句话,有笑话,名言什么的 (还有唐诗宋词sudo apt-get install fortune-zh)

    良月柒
  • Java 类加载机制详解

    Java 虚拟机一般使用 Java 类的流程为:首先将开发者编写的 Java 源代码(.java文件)编译成 Java 字节码(.class文件),然后类加载器...

    Java团长
  • 深入理解和探究Java类加载机制

    java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java 类,即 ...

    lyb-geek
  • Java虚拟机-02:类与类加载器

    摘要:本文主要介绍类加载器、类的唯一性、启动类加载器、拓展类加载器、应用程序类加载器。

    IT云清
  • 关于真机测试的几点感悟

    陨石坠灭
  • 读《深入理解Java虚拟机》解决实际问题及总结JDK和JVM整体架构

    以前看别人博客说看完《深入理解Java虚拟机》这本书并没有让自己的编程水平提高多少,不过却大大提高了自己的装逼水平。其实,我倒不这么认为,至少在我看完一遍这本书...

    Java小朔哥
  • Xposed加载JNI库

    用户1907613
  • 深入理解Java类加载器机制

    Java里面的类加载机制,可以说是Java虚拟机核心组件之一,掌握和理解JVM虚拟机的架构,将有助于我们站在底层原理的角度上来理解Java语言,这也是为什么我们...

    我是攻城师

扫码关注云+社区

领取腾讯云代金券