前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM的特性,通过代码来揭秘类加载器

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

作者头像
良月柒
发布2019-03-20 15:46:57
3610
发布2019-03-20 15:46:57
举报

技术文章第一时间送达!

类加载器

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

代码语言:javascript
复制
/**
 * 作者: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到底是个什么东西。

改造一下刚刚的程序:

代码语言:javascript
复制
/**
 * 作者: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它们之间的关系,改造一下刚刚的程序:

代码语言:javascript
复制
/**
 * 作者: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();
        }
    }

}执行结果:

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

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

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

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

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

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

看一下loadClass方法:

代码语言:javascript
复制
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类

代码语言:javascript
复制
/**
 * 作者: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 魔数

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

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员的成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类加载器
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档