类加载和双亲委派机制

类加载是什么?

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型

类加载的时机?

主动引用例子:

1、遇到new(用new实例对象),getStatic(读取一个静态字段),putstatic(设置一个静态字段),invokeStatic(调用一个类的静态方法)这四条指令字节码命令时
2、使用Java.lang.reflect包的方法对类进行反射调用时,如果此时类没有进行init,会先init。
3、当初始化一个类时,如果其父类没有进行初始化,先初始化父类
4、jvm启动时,用户需要指定一个执行的主类(包含main的类)虚拟机会先执行这个类
 
被动引用的例子:
1、通过子类来引用父类的静态字段,只会触发父类的初始化,不会触发子类的初始化
2、引用一个类的静态常量不会触发初始化,因为常量在编译阶段已经确认

类的生命周期?

加载 loading

>>>>连接(验证 verification 准备 preparation 解析 resolution)

>>>>初始化 initialization

>>>>使用 using

>>>>卸载 unloading

类加载过程

加载
          1.从Class文件获取二进制字节流
          2.将字节流中的静态结构转化为方法区的运行时的动态结构
          3.在内存中生成代表该Class的java.lang.Class对象,作为方法区该类的访问入口。
连接
          验证:验证Class文件的字节流中包含的信息是否符合JVM的要求,并确保不会危害JVM自身的安全。
          准备:为静态变量分配内存并赋初始值
          解析:将常量池内的符号引用转换为直接引用
初始化

          调用类的clinit()方法,为静态变量赋予实际的值,执行静态代码块

ClassLoader是什么?

它负责将 Class 的字节码形式(本质就是一个字节数组 byte[])转换成内存形式的 Class 对象

字节码有特定的复杂的内部格式,可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流

很多字节码加密技术就是依靠定制 ClassLoader 来实现的。先使用工具对字节码文件进行加密,运行时使用定制的 ClassLoader 先解密文件内容再加载这些解密后的字节码

每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的

class Class<T> {
  ...
  private final ClassLoader classLoader;
  ...
}

延迟加载

JVM 运行并不是一次性加载所需要的全部类,而是按需加载,也就是延迟加载

程序在运行的过程中会逐渐遇到很多不认识的新类,这时候就会调用 ClassLoader 来加载这些类

加载完成后就会将 Class 对象存在 ClassLoader 里面,下次就不需要重新加载了

JVM有哪些类加载器?

1、启动类加载器 BootstrapClassLoader 

负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,常用内置库 java.xxx.* 都在里面,比如 java.util.*、java.io.*、java.nio.*、java.lang.* 等等

这个 ClassLoader 比较特殊,由 C 代码实现,称之为「根加载器」

2、扩展类加载器 ExtensionClassLoader 

负责加载 JVM 扩展类,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头

3、应用程序类加载器 ApplicationClassloader(系统类加载器)

直接面向用户的加载器,加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载

使用ClassLoader类的getSystemClassLoader()方法即可获取这个加载器

双亲委派机制

当一个类加载器收到一个类加载请求时(加载.class文件),首先会把这个任务委托给父类加载器,每个类加载器递归这个操作,

只有在父类加载器在自己的搜索范围内找不到所需指定类时,子类加载器才会尝试自己去加载

俗称:"儿子"先不干,先让"老子"干,"老子"再让"老子"干....,直到干不了,再依次返回让"儿子"干

参看ClassLoader源码

class ClassLoader {
  ...
  private final ClassLoader parent;
  ...
}

工作过程:

 

 

1.当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。  

2.当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。  

3.如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载。  

4.如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。  

5.如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。  

6.如果均加载失败,就会抛出ClassNotFoundException异常。

 为什么要用双亲委派机制?

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了
不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
3、如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱
posted @ 2020-03-04 12:19  鼠标的博客  阅读(419)  评论(0编辑  收藏  举报