hello 我是宝哥,明天咱们来聊聊JVM的类加载过程
要搞清楚JVM,首先要搞清楚几个问题:
- jvm起到什么作用?
- 怎么加载class文件的?
- 加载类时会反复吗?程序是什么样的?
说到jvm 那么不得不提类的加载过程
.咱们先来理解下类是如何被一步一步加载到jvm的
类的加载过程
咱们先抽象的理解一下类加载的整个过程:
如上图所示,Java源代码文件(.java后缀)
会被Java编译器编译为字节码文件(.class后缀)
而后由JVM中的类加载器加载各个类的字节码文件,加载结束之后,交由JVM执行引擎执行。
在整个程序执行过程中,JVM用一段空间来存储程序执行期间须要用到的数据和相干信息,被称作为Runtime Data Area(运行时数据区
),也就是咱们常说的JVM内存。
此时,咱们能够全面的了解为:JVM为咱们的class字节码提供了加载
,存储
,执行
的环境.(jvm是java可跨平台运行的基石,因为不同的零碎有不同的jvm实现,都能够加载.class字节码文件)
Java的类加载机制
那么ClassLoader都做了什么呢?
咱们先来看看 类加载机制的定义:
虚拟机把形容类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终造成能够被虚拟机间接应用的 Java 类型,这就是虚拟机的类加载机制
这里有几个阶段比拟重要: 1.加载 2.连贯 3.初始化
依据这3个阶段,咱们能够分析出,类的生命周期
:
类的生命周期
加载
: 加载类的二进制字节流,并且将动态存储构造转化为办法区的运行时数据结构,而后在内存中生成一个代表此类的Class对象,作为办法区这个类各种数据的拜访入口验证
: 验证是在连贯(Linking)
局部的第一步,验证的目标是验证Class文件中的字节流合乎以后虚拟机的要求,保障不会危害虚拟机.筹备
: 为类变量
分配内存,并且设置类变量初始值,此时这此类变量所应用的内存都是在办法区中进行调配.解析
: 解析是将符号援用替换为间接援用,解析动作针对类或接口,字段,类或接口的办法进行解析。初始化
:初始化类或接口并且执行类或接口的初始化办法,此时,它的生命周期就开始了
初始化的机会:
- 虚拟机标准规定了有且只有
5 种状况
必须立刻
对类进行初始化 - 1.遇到new、getstatic 和 putstatic 或 invokestatic 这4条字节码指令时,如果类没有进行过初始化,则须要先触发其初始化。对应场景是:应用 new 实例化对象、读取或设置一个类的动态字段(被 final 润饰、已在编译期把后果放入常量池的动态字段除外)、以及调用一个类的静态方法。
- 2.对类进行反射调用的时候,如果类没有进行过初始化,则须要先触发其初始化。
- 3.当初始化类的父类还没有进行过初始化,则须要先触发其父类的初始化。(而一个接口在初始化时,并不要求其父接口全副都实现了初始化)
- 4.虚拟机启动时,用户须要指定一个要执行的主类(蕴含 main() 办法的那个类),虚构机会先初始化这个主类。
- 5.当应用 JDK 1.7 的动静语言反对时,如果一个 java.lang.invoke.MethodHandle 实例最初的解析后果 REF_getStatic、REF_putStatic、REF_invokeStatic 的办法句柄,并且这个办法句柄所对应的类没有进行过初始化,则须要先触发其初始化。
- 虚拟机标准规定了有且只有
应用
: 此时咱们能够通过new关键字,创立实例对象.顺带一提,一个Class对象总是会援用它的类加载器。调用Class对象的getClassLoader()办法,就能取得它的类加载器。由此可见,Class实例和加载它的加载器之间为双向关联关系。卸载
: 当类不再被援用或被垃圾回收器标记为已死对象时,将会被回收,然而Java虚拟机自身会始终援用类加载器,而这些类加载器则会始终援用它们所加载的类的Class对象,因而这些Class对象始终是可涉及的,也就是说jvm自带的类加载器所加载的类,在虚拟机还没有退出时,始终不会被卸载,当然也有特例 如:咱们本人定义的类加载器的类是能够被卸载的.
ClassLoader 类加载器
类的唯一性
任意一个类,都须要由加载它的类加载器
和这个类自身
一起确立其在Java虚拟机中的唯一性
这句定义怎么了解呢?
两个类来源于同一个 Class 文件,被同一个虚拟机加载,然而加载它们的类加载器不同
,那这两个类也不相等
那有的小伙伴就有纳闷了,还有很多类加载器吗?
emm..那加载的程序呢?会不会反复加载了?
别急,咱们理解下双亲委派准则
.
双亲委派准则
如果一个类加载器收到了类加载的申请,它首先不会本人去尝试加载这个类,而是把这个申请委派给父类加载器去实现,每一个档次的类加载器都是向上拜访,因而所有的加载申请最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈本人无奈实现这个加载申请(它的搜寻范畴中没有找到所需的类)时,子加载器才会尝试本人去加载。
这里举例几个面试会问的classloader职责:
Bootstrap ClassLoader
:根类加载器,负责加载java的外围类,它不是java.lang.ClassLoader的子类,而是由JVM本身实现;Extension ClassLoader
:扩大类加载器,扩大类加载器的加载门路是JDK目录下jre/lib/ext,扩大类的getParent()办法返回null,实际上扩大类加载器的父类加载器是根加载器,只是根加载器并不是Java实现的;System ClassLoader
:零碎(利用)类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path零碎属性或CLASSPATH环境变量所指定的jar包和类门路。程序能够通过getSystemClassLoader()来获取零碎类加载器;User Define ClassLoader
: 用户自定义的classloader,自定义的加载器必须继承ClassLoader。
它们的加载程序:
show me the code:
// 代码摘自《深刻了解Java虚拟机》 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 首先,查看申请的类是否曾经被加载过了,同时也解决了小伙伴的疑虑 Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 如果父类加载器抛出ClassNotFoundException // 阐明父类加载器无奈实现加载申请 } if (c == null) { // 在父类加载器无奈加载的时候 // 再调用自身的findClass办法来进行类加载 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
那么咱们不禁要思考一下为何要用这种准则有何长处?
- 1,一个当然是防止反复加载,晋升性能
- 2,防止了外围类被用户篡改(例如我在用户自定义的classloader中加载一个String类去笼罩自带的String类,因为先让父类加载,我定义的程序在后.不会呈现笼罩胜利的问题)
这里有几个点小伙伴须要留神,不然要被面试官吊打了:
- 类加载器之间的父子关系不会以继承的关系来实现,他们尽管都继承于抽象类
java.lang.ClassLoader
然而他们的关系是组合关系,应用组合关系复用父类的加载器. - Bootstrap 类加载器是用 C++ 实现的,
不继承于抽象类java.lang.ClassLoader
,它是虚拟机本身的一部分,如果获取它的对象,将会返回 null; - jvm自带的类加载器所加载的类,在虚拟机还没有退出时,始终不会被卸载,咱们本人定义的类加载器,加载的类是能够被卸载的.
毁坏双亲委派准则
当然类加载器的双亲委派准则是能够被毁坏的,毁坏它是因为双亲委派模型本身缺点导致,他没有方法解决用户根底类又要从新调用户类的代码。为了解决这个问题就有了线程上下文加载器,例如JNDI、JDBC、JCE等
举个Tomcat的例子:
每个Tomcat的webappClassLoader加载本人的目录下的class文件,不会传递给父类加载器。tomcat之所以造了一堆本人的classloader,大抵是出于上面三个起因:
- 对于各个 webapp中的 class和 lib,须要互相隔离,不能呈现一个利用中加载的类库会影响另一个利用的状况,而对于许多利用,须要有共享的lib以便不浪费资源。
- 与 jvm一样的安全性问题。应用独自的 classloader去装载 tomcat本身的类库,免得其余歹意或无心的毁坏;
- 热部署。置信大家肯定为 tomcat批改文件不必重启就主动从新装载类库而惊叹吧。
毁坏双亲委派的形式
双亲委派机制准则在loadclass办法中。
只须要绕开loadclass办法中即可。
- 自定义类加载器 ,重写loadclass办法
- 应用SPI机制绕开loadclass 办法。以后线程设定关联类加载器
对于SPI在我另外一篇文章https://mp.weixin.qq.com/s/2U...
讲了那么久的加载,此时咱们才刚刚一只脚踏进JVM的大门,前面咱们将剖析JVM运行时数据区
.大家继续关注java宝典
公众号,咱们下章再聊
最近我创立了一个微信-学习群,有酷爱学习的小伙伴能够进群探讨
关注公众号:java宝典