前言
前几篇文章讲解类的加载器分类,本篇文章我们就要测试不同的类加载器、以及分析源码
测试不同的类加载器
每个Class对象都会包含一个定义它的ClassLoader的一个引用
获取ClassLoader有以下几种途径方法,如下:
获取当前类的ClassLoader:clazz.getClassLoader()
获取当前线程上下文的ClassLoader:Thread.currentThread().getContextClassLoader()
获取系统的ClassLoader:ClassLoader.getSystemClassLoader()
接下来我们通过示例代码来体会一下上面所讲的加载器
public class classLoaderTest1{
public static void main(string[] args){
//加载系统类加载器
ClassLoader systemclassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemclassLoader);
//获取系统的父类加载器(不是真正意义上继承父类的加载器)
ClassLoader extclassLoader = systemclassLoader.getParent();
System.out.println(extclassLoader);
//获取引导类加载器
ClassLoader bootstrapClassLoader = extclassLoader.getParent();
System.out.print1n(bootstrapc1assLoader);
}
}
//运行结果如下:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null
站在程序的角度看,引导类加载器与另外两种类加载器(系统类加载器和扩展类加载器)并不是同一个层次意义上的加载器
,引导类加载器是使用C++语言编写而成的
而另外两种类加载器则是使用Java语言编写而成的。由于引导类加载器压根儿就不是一个Java类,因此在Java程序中只能打印出空值(null)
。
那么我们接着来看看关于数组类型的加载是怎么样的?
public class classLoaderArrTest1{
public static void main(string[] args){
//关于数组类型的加载
String[] arrstr = new String[10];.
System.out.println(arrstr.getclass());
System.out.println(arrstr.getclass().getClassLoader();
}
}
//运行结果如下:
class [Ljava.lang.String;
null
数组类的Class对象,不是由类加载器去创建的,而是在Java运行期JVM根据需要自动创建的
对于数组类的类加载器来说,是通过Class.getClassLoader()返回的,与数组当中元素类型的类加载器是一样的;如果数组当中的元素类型是基本数据类型,数组类是没有类加载器的
。
我们讲解类的生命周期的时候说道过:在Java中数据类型分为基本数据类型和引用数据类型。基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载
感兴趣的小伙伴可以进行访问:点击进入文章
ClassLoader注释解析与Launcher分析
除了以上虚拟机自带的加载器外,用户还可以定制自己的类加载器。Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类
接下来让我们打开ClassLoader类来进行解读类信息
接下来我们在对Launcher进行初步的判断
对于这两个加载器我们创建的话,是放在Launcher()构造器当中
这时我们就可以知道扩展类对于parent 加载器传递的是null值,接下来再看看下一个
ClassLoader主要方法分析
抽象类ClassLoader内部没有抽象方法,我们首先介绍以下方法
//返回该类加载器的超类加载器
public final ClassLoader getParent():
//加载全类名为name的类,返回结果为java.lang .class类的实例
//如果找不到类,则返回ClassNotFoundException异常
//该方法中的逻辑就是双亲委派模式的实现
//可能是字节码文件也可能是二进制流
public Class<?> loadclass(string name) throws ClassNotFoundException
接下来我们打开ClassLoader类的loadClass方法
当我们系统加载器,调用父类加载器加载的话(扩展器),接着满足条件调用加载器进行加载(启动类),但此时并没有父类加载器(启动类加载器)则进入findBootstrapClassOrNull
方法
此时类加载器(扩展类加载器)返回null,那么就接着往下走
此时类加载器(系统加载器)收到返回的结果(扩展器加载器返回null),接着回到上一步
那么我们针对于这个findClass方法,解读一下看看
//查找二进制名称为name的类,返回结果为java.lang.Class类的实例。
//这是一个受保护的方法,JVM鼓励我们重写此方法
//需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass()方法调用
protected class<?> findclass(string name) throws ClassNotFoundException
发现该方法不是抽象的,但是没有任何代码,所以需要子类去重写,那么我们看看谁重写了
在DK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,从而实现自定义的类加载类。但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中
从前面的分析可知I findClass()方法是在loadClass()方法中被调用的,当loadClass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载
,这样就可以保证自定义的类加载器也符合双亲委托模式
需要注意的是ClassLoader类中并没有实现findClass()方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常,同时应该知道的是findClass方法通常是和defineClass方法一起使用的。一般情况下,在自定义类加载器时、会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()分法生成类的Class对象
接下来我们来看看URLClassLoader里是怎么重写findClass方法的
//根据给定的字节数组b转换为Class的实例
//off和len参数表示实际Class信息在byte数组中的位置和长度
//其中byte数组b是ClassLoader从外部获取的
//这是受保护的方法,只有在自定义ClapsLoader子类中可以使用
protected final Class<?> defineClass(String name, byte[] b, int off,int len)
defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象
(ClassLoader中已实现该方法逻辑),通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象。
defineClass()方法通常与findClass()方法一起使用,一般情况下,在自定义类加载器时,会直接覆盖 ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象
还有以下三个我们刚刚未提到过的方法如下:
//链接指定的一个Java类
//使用该方法可以使用类的Class对象创建完成的同时也被解析。
//前面我们说链接阶段主要是对字节码进行验证
//为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用
protected final void resoeclass(class<?> c)
//查找名称为name的已经被加载过的类
//返回结果为java.lang.Class类的实例。
//这个方法是final方法,无法被修改。
protected final class<?> findLoadedclass(String name)
//它也是一个classLoader的实例
//这个字段所表示的ClassLoader也称为这个ClassLoader的双亲
//在类加载的过程中,classLoader可能会将某些请求交予自己的双亲处理。
private final classLoader parent;
SecureClassLoader与URLClassLoader子类
SecureClassLoader扩展了ClassLoader,新增了几个与使用相关的代码源(对代码源的位置及其证书的险证)和权限定义类验证(主要指对class源码的访问权限)的方法
一般我们不会直接跟这个类打交道,更多是与它的子类URLClassLoader有所关联。
前面说过ClassLoader是一个抽象类,很多方法是空的没有实现,比如 findClass()、findResource()
等
而URLClassLoader这个实现类为这些方法提供了具体的实现,并新增了URLClassPath类协助取得Class字节码流等功能
在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClass()方法及其获取字节码流的方式
,使自定义类加载器编写更加简洁。
ExtClassLoader与AppClassLoader子类
了解完URLClassLoader后接着看看剩余的两个类加载器,即拓展类加载器ExtclassLoader和系统类加载器AppClassLoader,这两个类都继承自URLClassLoader,是sun.misc.Launcher的静态内部类
sun.misc.Launcher主要被系统用于启动主应用程序,ExtClassLoader和AppClassLoader都是由sun.misc.Launcher创建的,其类主要类结构如下:
我们发现ExtClassLoader并没有重写loadClass()方法,这足矣说明其遵循双亲委派模式
而AppClassLoader重载了loadClass()方法,但最终调用的还是父类loadClass()方法,因此依然遵守双亲委派模式。
Class.forName()与ClassLoader. loadClass()
Class.forName()是一个静态方法,最常用的是Class.forName( String className )
根据传入的类的全限定名返回一个Class 对象。该方法在将Class文件加载到内存的同时,会执行类的初始化
如:Class.forName( “com.atguigu.java.Helloworld” )
ClassLoader.loadClass()这是一个实例方法,需要一个ClassLoader对象来调用该方法
该方法将class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化
该方法因为需要得到一个ClassLoader对象
所以可以根据需要指定使用哪个类加载器.如: ClassLoader cl=… .;c1.loadclass( “com.atguigu.java. Helloworld” );
同时我们前几篇文章讲解类的主动使用与被动使用的时候,有提到过:调用classLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化