类加载机制

.class 中保存着java 代码经过编译转换后的虚拟机指令,当需要用到某个类时,虚拟机会加载该类的 .class 文件。创建对应的 class 对象
将 .class 文件加载到虚拟机内存,该过程称为 类加载 。

加载:
  通过一个类的权限定类名,找到该类的.class 字节码文件。利用此 字节码文件 创建一个 class 对象。
验证:
  确保 .class 文件中的字节流包含的信息符合当前虚拟机的要求。不会危害虚拟机自身安全。
  主要包括4种验证:文件格式验证,元数据验证,字节码验证,符号引用验证。
准备:
  为类变量(static 变量)分配内存,并设置初始值。
    如:static int i=5; 此处只将i初始化成0,5的值在 初始化 阶段赋值。
  这里不包含 final 修饰的 static 变量。 
    因为 final 在编译时就会分配内存。
  此处不会为实例变量(该类的对象私有的变量)设置初始化。类变量分配在方法区中,实例变量随对象一起分配在 堆内存 中。
解析:
  将常量池中的 符号引用 替换为 直接引用 的过程。
    符号引用:一组符号用来描述目标,可以是任何字面量。
    直接引用:指向目标的指针。
初始化:
  类加载最后阶段,若该类具有超类,则对其进行初始化。
  执行静态代码块 和 静态初始化变量。
    在准备阶段设置了默认初始值的 static 变量,将在该阶段真正赋值。成员变量也将被初始化。



类加载器的作用
  根据一个类的权限定类名,加载该类的二进制字节码(.class)文件到 JVM 内存中,创建一个该类的java.lang.Class对象实例。
JVM提供三种类加载器:
  引导(Bootstrap)类加载器
  扩展(Extension)类加载器
  系统(System)类加载器(也称应用类加载器)

启动类加载器:
  主要加载的是JVM自身需要的类。这个类加载使用C++是实现,是虚拟机自己的一部分。
  负责将  <JAVA_HOME>/lib 路径下的核心类库或 -Xbootclasspath 参数指定的路径下的jar包加载到内存中。
注意:
  由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的。
  (出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。)

扩展加载器:
  扩展类加载器是指Sun公司实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类。
  负责加载 <JAVA_HOME>/lib/ext 目录下或者由系统变量 -Djava.ext.dir 指定位路径中的类库。

系统加载器:
  也称应用程序加载器,SUN公司实现的sun.misc.Launcher$AppClassLoader。
  负责加载系统类路径 java-classpath-D java.class.path 指定路径下的类库,也就是我们经常用到的 classpath 路径。
  一般情况下,是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。
  日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的。
注意:
  JVM对class文件采用的是 按需加载 的方式。也就是,当需要用到该类的时候,才会将它的class文件加载到内存中生成CLass对象。
  加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理。它是一种任务委派模式。


双亲委派机制
概述:
  双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。
  双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码。
原理:
  如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
  如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。
优势、好处:
1、Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,如果父类已经加载了该类,子类计就没有必要在加载一次。
2、java核心api中定义类型不会被随意替换。
情况一:
  假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
情况二:
  如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常。
  java.lang.SecurityException: Prohibited package name: java.lang

类加载器体系

ClassLoader类重要方法:
1、loadClass()方法是ClassLoader类自己实现的,该方法中的逻辑就是双亲委派模式的实现。
  先从缓存查找该class对象,找到就不用重新加载。
  如果找不到,则委托给父类加载器去加载。
  如果没有父类,则委托给启动加载器去加载。
  如果都没有找到,则通过自定义实现的findClass去查找并加载。
如果不想重新定义加载类的规则,也没有复杂的逻辑,只想在运行时加载自己指定的类,使用this.getClass().getClassLoder.loadClass("className"),可以获取到class对象。

2、findClass(String)
当loadClass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载,这样就可以保证自定义的类加载器也符合双亲委托模式。
注意:
ClassLoader类中并没有实现findClass()方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常。
应该知道的是findClass方法通常是和defineClass方法一起使用的。

3、defineClass(byte[]b, int off, int len) 
用来将byte字节流解析成JVM能够识别的Class对象(ClassLoader中已实现该方法逻辑)。
通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象。如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象,defineClass()方法通常与findClass()方法一起使用。
一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象。

4、resolveClass(Class≺?≻ c) 
该方法可以使用类的Class对象创建完成也同时被解析。

 

posted @ 2019-03-27 10:09  payn  阅读(185)  评论(0)    收藏  举报