1.类加载器的双亲委托模型
在JVM中,类加载器使用双亲委托机制进行类加载,每一个类加载器实例都有一个与之对应的父类加载器,当类加载器需要加载类时,类加载器在加载类之前会把加载类的操作委托给父类加载器执行。类加载器的层次结构如下图所示。
虚拟机内建的类加载器称为启动类加载器(bootstrap class loader),它没有父类加载器,但可以作为其他类加载器的父类加载器。它主要加载存放在<JAVA_HOME>/lib目录中的、或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库(如rt.jar)。
扩展类加载器加载<JAVA_HOME>/lib/ext目录中的或者被java.ext.dirs系统变量所指定的路径中的所有类库。
应用程序类加载器也称为系统类加载器,负责加载用户类路径上所指定的类库。
获得类加载器的途径有以下方式:
获得当前类的ClassLoader:clazz.getClassLoader() 获得当前线程上下文的ClassLoader:Thread.currentThread().getContextClassLoader() 获得系统的ClassLoader:ClassLoader.getSystemClassLoader() 获得调用者的ClassLoader:DriverManager.getCallerClassLoader()
2.自定义类加载器
在Java中,类加载器对应的类为ClassLoader,ClassLoader是一个抽象类,自定义类加载器需继承ClassLoader类。一个类加载器通过一个类的binary name(如:“java.lang.String”,javax.swing.JSpinner$DefaultEditor")去定位或产生这个类的数据,类加载器通过读取磁盘上的class文件创建出类对象。
在JVM中,数组类的Class对象不是由类加载器创建的,而是由java虚拟机在需要时自动创建。数组类的Class.getClassLoader()方法的返回值与数组中元素的Class.getClassLoader()方法返回值相同,如果数组中的元素是原生类型,那么这个数组类没有类加载器,返回null。
代码
public static void main ( String[ ] args) {
int [ ] array1 = new int [ 1 ] ;
System. out. println ( array1. getClass ( ) . getClassLoader ( ) ) ;
System. out. println ( "------------" ) ;
String[ ] array2 = new String [ 1 ] ;
System. out. println ( array2. getClass ( ) . getClassLoader ( ) ) ;
System. out. println ( "------------" ) ;
A[ ] array3 = new A [ 1 ] ;
System. out. println ( array3. getClass ( ) . getClassLoader ( ) ) ;
}
输出
null
-- -- -- -- -- --
null
-- -- -- -- -- --
sun. misc. Launcher$AppClassLoader@18b4aac2
在以上代码中,int为原生类型,类加载器为null;String类位于rt.jar中,对应的类加载器为启动类加载器,启动类加载器通常由C实现,在Java中没实际类实例来表示,所以此处显示null;用户自定义的类其数组的类加载器与类的加载器相同,为应用类加载器。
用户自定义一个类加载器,需要继承ClassLoader抽象类,然后重写其中的findClass方法,代码如下:
public class MyClassLoader extends ClassLoader {
private String path;
private String loaderName;
private static final String fileExtension = ".class" ;
public MyClassLoader ( String loaderName) {
super ( ) ;
this . loaderName = loaderName;
}
public void setPath ( String path) {
this . path = path;
}
@Override
protected Class< ? > findClass ( String name) throws ClassNotFoundException {
byte [ ] data = loadClassData ( name) ;
return this . defineClass ( name, data, 0 , data. length) ;
}
private byte [ ] loadClassData ( String className) {
byte [ ] data = null;
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
className = className. replace ( "." , "/" ) ;
try {
inputStream = new FileInputStream ( new File ( this . path + className + fileExtension) ) ;
byteArrayOutputStream = new ByteArrayOutputStream ( ) ;
int i;
while ( ( i = inputStream. read ( ) ) != - 1 ) {
byteArrayOutputStream. write ( i) ;
}
data = byteArrayOutputStream. toByteArray ( ) ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
} finally {
try {
inputStream. close ( ) ;
byteArrayOutputStream. close ( ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
return data;
}
}
在构造方法中,调用了父类的构造方法,在父类的构造方法中,我们自定义的类加载器其父类加载器默认被指定为了应用类加载器(ClassLoader.getSystemClassLoader())。setPath方法用来指定需要加载的类的.class文件的路径。在重写的findClass方法中,首先读取磁盘中的.class文件,将其转为字节数组,然后调用ClassLoader.defineClass把字节数据转为Class对象。
Main方法中的执行代码如下:
public static void main ( String[ ] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader myClassLoader = new MyClassLoader ( "MyClassLoader" ) ;
myClassLoader. setPath ( "/Users/wby/Desktop/" ) ;
Class clazz = myClassLoader. loadClass ( "jvm.classloader.Person" ) ;
Object p = clazz. newInstance ( ) ;
System. out. println ( p. getClass ( ) . getClassLoader ( ) ) ;
System. out. println ( p) ;
}
在执行代码中,首先创建了一个自定义的类加载器的对象,然后指定类的存放路径,我们把需要加载的类的class文件放在了桌面上,然后指定该类的名称,接着执行loadClass方法,加载该类,得到结果如下。
sun. misc. Launcher$AppClassLoader@18b4aac2
Person{ name= 'null' , age= 0 }
在输出中,得到了类的对象,但是其类加载器为应用类加载器,我们自定义的类加载器没有使用到。查看loadClass方法的部分源码如下。
Class< ? > c = findLoadedClass ( name) ;
if ( c == null) {
long t0 = System. nanoTime ( ) ;
try {
if ( parent != null) {
c = parent. loadClass ( name, false ) ;
} else {
c = findBootstrapClassOrNull ( name) ;
}
} catch ( ClassNotFoundException e) {
}
if ( c == null) {
long t1 = System. nanoTime ( ) ;
c = findClass ( name) ;
sun. misc. PerfCounter. getParentDelegationTime ( ) . addTime ( t1 - t0) ;
sun. misc. PerfCounter. getFindClassTime ( ) . addElapsedTimeFrom ( t1) ;
sun. misc. PerfCounter. getFindClasses ( ) . increment ( ) ;
}
}
在调用loadClass方法后,程序首先执行findLoadedClass方法查看类是否已被加载,如果没有被加载,会调用parent.loadClass方法,即调用父类加载器去加载这个类,最终会调用到findBootstrapClassOrNull方法使用启动类加载器加载,如果启动类加载器和其他的父类加载器一直没有加载成功,才会调用findClass方法。在之前的输出结果中,由于经过编译之后,用户类路径上存在Person.class文件,应用类加载器会从这个路径上成功加载这个类,轮不到自定义的类加载器去加载它,因此结果会输出AppClassLoader。
如果需要使用自定义类加载器去加载这个类,需要删除用户类路径上存在Person.class文件,只保留setPath方法中设置的类路径"/Users/wby/Desktop/"下的jvm.classloader.Person.class,然后再次执行,结果如下,此时就使用了自定义的类加载器。
jvm. classloader. MyClassLoader@610455d6
Person{ name= 'null' , age= 0 }