Java虚拟机--类加载器

如何加载一个Class文件

在之前的文章中,笔者介绍了Java虚拟机--类加载机制,阐述了一个类加载到底做了哪些事情!

但是,关于类加载器的只是,并没有做任何介绍,只是说了下会在后面的文章中进行单独阐述。那么,本篇的意义就是来告诉大家类加载器的实现。

首先,我们来简单的回顾下类加载机制中的内容。

类加载机制

虚拟机把类的数据从.class文件加载到内存,并对class文件中的数据进行校验、转换、解析、初始化等操作后,最终形成可以被虚拟机识别并使用的Class对象的过程就叫做“虚拟机的类加载”,主要包括为3大阶段。

类加载机制

阶段一:加载

加载,类加载器通过类的全限定名来获取类的二进制字节流,获取的方式可以通过jar包、war包、网络、JSP文件中获取,绝大部分情况下是通过jar包、war包中获取。

获取到字节流后,会将字节流中的信息转化为方法区中的运行时数据结构。在内存中,生成代表该类的Class对象,作为访问该类的数据入口。

阶段二:连接

连接比较复杂,分为3个小阶段:

验证:确保被加载类的正确性,即确保被加载的类符合javac编译的规范,可编译通过的代码。

准备:为类的静态变量分配内存,并初始化为默认值(零值)。

解析:将类中的符号引用转化为直接引用。

阶段三:初始化

为类的静态变量赋值,与连接阶段中的的准备不同。此阶段,代码可debug查看。

如int类型的静态变量static int x = 3,连接阶段赋零值即为0,而初始化阶段赋值即为3。

以上就是类加载机制的三大阶段,而我们今天要将的类加载器存在于阶段一中--加载。可以说,没有类加载器也就没有了后续的流程,类加载器在Java虚拟机中起到了至关重要的作用。

类加载器

类加载器(class loader)将Java类从本地磁盘加载到Java虚拟机中,并同时创建了该类的Class对象,实现了“通过一个类的全限定类名来获取此类的二进制字节流”功能。

类加载器是Java语言的一项创新,也是Java语言流程的重要原因之一,在类层次划分、OSGI、热部署、代码加密等领域有着重要的作用,成为Java不可或缺的一部分。

首先,我们来写一个测试类,来看下类加载器,ClassLoaderTest测试类:

public class ClassLoaderTest { 
   public static void main(String[] args) { 
       ClassLoader loader = ClassLoaderTest.class.getClassLoader(); 
       while (true) { 
           System.out.println(loader);
            if(loader==null){
                break;
            }
            loader = loader.getParent();
       } 
   } 
}

运行结果:

sun.misc.Launcher$AppClassLoader@41dee0d7

sun.misc.Launcher$ExtClassLoader@f7b650a

null

首先获取到的是AppClassLoader类加载器,紧接着又获取的是ExtClassLoader类加载器,最后获取的对象为null

为什么为null呢,后续来解答!

接下来,我们来看看在Java体系中到底有哪些类加载器。

类加载的分类

在Java中,类加载器可以分为两大类,一类是由Java系统提供的,另外一类是自定义的,由开发人员编写提供的。

系统类加载器:

引导类加载器(bootstrap class loader):用来加载Java的核心库,由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作,不继承自java.lang.ClassLoader(这就是上面例子中为什么最后取到的对象为null的原因)。负责加载<Java_Runtime_Home>/lib下面的类库到内存中,或-Xbootclasspath选项指定的jar包装入内存;

扩展类加载器(extensions class loader):用来加载Java的扩展库,由sun.misc.Launcher$ExtClassLoader来实现。负责加载
<Java_Runtime_Home>/lib/ext,或-Djava.ext.dirs选项指定目录下的jar包装入到内存中;

系统类加载器(system class loader):用来加载Java应用的类路径(CLASSPATH)的Java类,由sun.misc.Launcher$AppClassLoader来实现。一般来说,Java应用中的类都是由它来完成加载的,可以通过ClassLoader.getSystemClassLoader()来获取。

自定义类加载器:

自定义类加载器(User Custom ClassLoader):开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。在程序运行期间, 通过自定义的java.lang.ClassLoader子类动态加载class文件。

java.lang.ClassLoader类介绍

方法 说明
getParent() 返回该类加载器的父类加载器。
loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class<?> c) 链接指定的 Java 类。

以上为ClassLoader对于类加载功能的主要方法介绍。

在我们的应用程序中,都是由这4种类加载器互相配合进行加载,这4种类加载器在虚拟机中维护了一种父子关系,这种关系叫做“双亲委派模型”。下面,我们就来看看什么是双亲委派模型。

双亲委派模型

下面的图片中,展示的就是“双亲委派模型”,模型中呈现出Java体系架构中的四大类加载器的关系,除了顶层的引导类加载器之外,其余类加载都需要有父加载器存在,但是此子父类关系并不是通过java代码中继承的方式实现。具体如何实现,后面讲解。

1526024942(1).png

知道了类加载器的结构模型,那么该模型在代码整个Java体系中如何工作呢?

工作流程:一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个类加载请求委派给其父类加载器去完成,每一个层的类加载器都是如此,依次向父类加载器传递,最终所有的类加载请求都会传送到顶层的启动类加载器(bootstrap)中,只有当父加载器反馈无法完成这个类加载请求时,子类加载器才会尝试自己去进行类加载操作,如果子类加载器也依旧无法完成,则代码层面就会抛出异常。

此时,你会不会感到疑惑?为啥儿子自己的活不去干,而首先交给他爹去完成呢?这么做的目的何在?

在Java体系中,双亲委派模型保证了类的唯一性,将Java类与它的类加载器绑定到了一起,当父类加载器加载完成后,子类加载器不会再次加载。此外,双亲委派模型还保证了Java框架的安全性。例如:java.lang.Object类,无论是上述哪个类加载器要加载这个类,最终都会委派给模型中的启动类加载器去加载,因此java.lang.Object类在程序中保证了唯一性。

相反,如果没有使用该模型,而是由各个类加载器自行去加载的话,那么系统中就会出现不同的java.lang.Object类,类的唯一性被打破,Java体系中的基本行为就得不到保证。例如:,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。涉及到“类相等”的方法有:Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法以及instanceof对象所属关系判定。

试想一下,如果我们自定义一个java.lang.Object类会怎么样?(其实我们自定义的java.lang.Object类无法在程序中被导入,只能模拟定义java.lang.Object类--java.lang.ObjectTest)

当JVM请求类加载进行自定义的类加载时,双亲委派模型会将请求传递到启动类加载器中,但是启动类加载器默认只加载<Java_Runtime_Home>/lib路径下的类,在该路径下并没有ObjectTest类,所以启动类加载器无法加载,只能向下传递给子类加载器,最终会将请求传递到系统类加载器中,但是系统类加载器也无法进行加载,会抛出异常。

为什么,why?

这是因为以java.开头的是核心API包,需要访问权限,强制加载会抛出异常,任何以java.开头的包都会报错:

Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
image

此异常是代码层面抛出的,并不是native方法虚拟机底层抛出,源码可见(ClassLoader类):

if ((name != null) && name.startsWith("java.")) {
        throw new SecurityException
            ("Prohibited package name: " +
             name.substring(0, name.lastIndexOf('.')));
}

此时,你会不会又突发奇想,我自己定义一个类,放在<Java_Runtime_Home>/lib路径下会如何?

image

编写代码,并打成jar包,jar包的名称就叫做 jiaboyan.jar:

jar cvf jiaboyan.jar com\jiaboyan\test\ObjectTest.class

接下来,把jiaboyan.jar包放入到<Java_Runtime_Home>/lib下,也同时放入到<Java_Runtime_Home>/lib/ext,并同时保留截图中的代码,如图所示:

image

执行main()方法,结果如下:

sun.misc.Launcher$AppClassLoader@8fd9b4d

从输出可以看出,放置到<Java_Runtime_Home>/lib目录下的ObjectTest.class类并没有被启动类加载器加载,而是由扩展类加载器加载了。

why?不是说了委派给最顶层的类加载进行加载吗?其实,这是由于虚拟机出于安全角度考虑,不会加载<Java_Runtime_Home>/lib中的陌生类,开发者把自定义的类放置到此目录下启动类加载器是不会进行加载的。

下一篇,将会对类加载器源码进行分析!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容