前言

明天咱们来讲讲jvm类加载的过程,咱们写了那么多类,却不晓得类的加载过程,岂不是很难堪

jvm的启动是通过疏导类加载器(bootstrap class loader)创立一个初始类(initial class)来实现的,这个类是由jvm的具体实现指定的。[来自官网标准]

jvm组成构造之一就是类装载器子系统,咱们明天就来认真讲讲这个组件。

Java代码执行流程图

大家通过这个流程图,理解一下咱们写好的Java代码是如何执行的,其中要经验类加载器这个流程,咱们就来认真讲讲这外面的知识点。

类加载子系统

类加载零碎架构图

临时看不懂这两张图没关系,跟着老哥往下看

类的生命周期

类的生命周期包含:加载、链接、初始化、应用和卸载,其中加载链接初始化,属于类加载的过程,咱们上面认真解说。应用是指咱们new对象进行应用,卸载指对象被垃圾回收掉了。

类加载的过程

  • 第一步:Loading加载
通过类的全限定名(包名 + 类名),获取到该类的.class文件的二进制字节流

将二进制字节流所代表的动态存储构造,转化为办法区运行时的数据结构

内存中生成一个代表该类的java.lang.Class对象,作为办法区这个类的各种数据的拜访入口

总结:加载二进制数据到内存 —> 映射成jvm能辨认的构造 —> 在内存中生成class文件

  • 第二步:Linking链接

链接是指将下面创立好的class类合并至Java虚拟机中,使之可能执行的过程,可分为验证筹备解析三个阶段。

① 验证(Verify)

确保class文件中的字节流蕴含的信息,合乎以后虚拟机的要求,保障这个被加载的class类的正确性,不会危害到虚拟机的平安。

② 筹备(Prepare)

为类中的动态字段分配内存,并设置默认的初始值,比方int类型初始值是0。被final润饰的static字段不会设置,因为final在编译的时候就调配了

③ 解析(Resolve)

解析阶段的目标,是将常量池内的符号援用转换为间接援用的过程(将常量池内的符号援用解析成为理论援用)。如果符号援用指向一个未被加载的类,或者未被加载类的字段或办法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。)

事实上,解析器操作往往会随同着 JVM 在执行完初始化之后再执行。 符号援用就是一组符号来形容所援用的指标。符号援用的字面量模式明确定义在《Java 虚拟机标准》的Class文件格式中。间接援用就是间接指向指标的指针、绝对偏移量或一个间接定位到指标的句柄。

解析动作次要针对类、接口、字段、类办法、接口办法、办法类型等。对应常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。

  • 第三步:initialization初始化
初始化就是执行类的结构器办法init()的过程。

这个办法不须要定义,是javac编译器主动收集类中所有类变量的赋值动作和动态代码块中的语句合并来的。

若该类具备父类,jvm会保障父类的init先执行,而后在执行子类的init

类加载器的分类

  • 第一个:启动类/疏导类:Bootstrap ClassLoader
这个类加载器应用C/C++语言实现的,嵌套在JVM外部,java程序无奈间接操作这个类。

它用来加载Java外围类库,如:JAVA_HOME/jre/lib/rt.jarresources.jarsun.boot.class.path门路下的包,用于提供jvm运行所需的包。

并不是继承自java.lang.ClassLoader,它没有父类加载器

它加载扩大类加载器应用程序类加载器,并成为他们的父类加载器

出于平安思考,启动类只加载包名为:java、javax、sun结尾的类

  • 第二个:扩大类加载器:Extension ClassLoader
Java语言编写,由sun.misc.Launcher$ExtClassLoader实现,咱们能够用Java程序操作这个加载器

派生继承自java.lang.ClassLoader,父类加载器为启动类加载器

从零碎属性:java.ext.dirs目录中加载类库,或者从JDK装置目录:jre/lib/ext目录下加载类库。咱们就能够将咱们本人的包放在以上目录下,就会主动加载进来了。

  • 第三个:应用程序类加载器:Application Classloader
Java语言编写,由sun.misc.Launcher$AppClassLoader实现。

派生继承自java.lang.ClassLoader,父类加载器为启动类加载器

它负责加载环境变量classpath或者零碎属性java.class.path指定门路下的类库

它是程序中默认的类加载器,咱们Java程序中的类,都是由它加载实现的。

咱们能够通过ClassLoader#getSystemClassLoader()获取并操作这个加载器

  • 第四个:自定义加载器
个别状况下,以上3种加载器能满足咱们日常的开发工作,不满足时,咱们还能够自定义加载器

比方用网络加载Java类,为了保障传输中的安全性,采纳了加密操作,那么以上3种加载器就无奈加载这个类,这时候就须要自定义加载器

自定义加载器实现步骤

继承java.lang.ClassLoader类,重写findClass()办法

如果没有太简单的需要,能够间接继承URLClassLoader类,重写loadClass办法,具体可参考AppClassLoaderExtClassLoader

获取ClassLoader几种形式

它是一个抽象类,其后所有的类加载器继承自 ClassLoader(不包含启动类加载器)

// 形式一:获取以后类的 ClassLoaderclazz.getClassLoader()// 形式二:获取以后线程上下文的 ClassLoaderThread.currentThread().getContextClassLoader()// 形式三:获取零碎的 ClassLoaderClassLoader.getSystemClassLoader()// 形式四:获取调用者的 ClassLoaderDriverManager.getCallerClassLoader()

类加载机制—双亲委派机制

jvm对class文件采纳的是按需加载的形式,当须要应用该类时,jvm才会将它的class文件加载到内存中产生class对象。

在加载类的时候,是采纳的双亲委派机制,即把申请交给父类解决的一种工作委派模式。

  • 工作原理

(1)如果一个类加载器接管到了类加载的申请,它本人不会先去加载,会把这个申请委托给父类加载器去执行。

(2)如果父类还存在父类加载器,则持续向上委托,始终委托到启动类加载器:Bootstrap ClassLoader

(3)如果父类加载器能够实现加载工作,就返回胜利后果,如果父类加载失败,就由子类本人去尝试加载,如果子类加载失败就会抛出ClassNotFoundException异样,这就是双亲委派模式

  • 第三方包加载形式:反向委派机制

在Java利用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口容许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 外围库,个别存在rt.jar包中,由Bootstrap类加载器加载。而Bootstrap类加载器无奈间接加载SPI的实现类,同时因为双亲委派模式的存在,Bootstrap类加载器也无奈反向委托AppClassLoader加载器SPI的实现类。在这种状况下,咱们就须要一种非凡的类加载器来加载第三方的类库,而线程上下文类加载器(双亲委派模型的破坏者)就是很好的抉择。

从图可知rt.jar外围包是有Bootstrap类加载器加载的,其内蕴含SPI外围接口类,因为SPI中的类常常须要调用内部实现类的办法,而jdbc.jar蕴含内部实现类(jdbc.jar存在于classpath门路)无奈通过Bootstrap类加载器加载,因而只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相干类应用。显然这种线程上下文类加载器的加载形式毁坏了“双亲委派模型”,它在执行过程中摈弃双亲委派加载链模式,使程序能够逆向应用类加载器,当然这也使得Java类加载器变得更加灵便。

  • 沙箱平安机制

自定义 String 类,然而在加载自定义 String 类的时候会率先应用疏导类加载器加载,而疏导类加载器在加载的过程中会先加载 JDK 自带的文件(rt.jar 包中的 javalangString.class),报错信息说没有 main 办法就是因为加载的 rt.jar 包中的 String 类。这样能够保障对 Java 外围源代码的爱护,这就是沙箱平安机制。

IT 老哥

我是一个通过大学四年自学,进入大厂做高级java开

发的程序员。关注我,每天分享技术干货,助你进大

厂。我能够,你也能够的。

如果你感觉文章写的还不错,请帮老哥点个赞!