ClassLoader深入学习记录

Posted by Csming on 2017-02-28

最近为了学习Java底层,因而学习了ClassLoader;准备下一阶段学习JVM原理


ClassLoader是什么

  • ClassLoader是Java的类加载机制
    ClassLoader用于动态加载class文件到内存中

Java程序写好后,是由若干个class文件组织而成的一个完整的Java应用程序;程序运行时,会调用改程序的一个入口函数来调用系统的相关功能;这些功能被封装在不同的class文件中;程序启动时,不会一次性加载程序所需要的所有class文件,而是根据需要,通过ClassLoader来动态加载某个class文件到内存中;当class文件被载入到内存后,才能被其他class所引用;

Java提供的ClassLoader

  • Java提供的ClassLoader有三个
    **BootStrap ClassLoader(启动类加载器):**是Java类加载层最顶层的类加载器,由C++编写而成, 已经内嵌到JVM中了;主要用来加载JDK的核心类库,如:rt.jar、resources.jar、charsets.jar等;
    eg:利用以下代码获得BootStrap ClassLoader加载的jar或class文件
1
2
3
4
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();  
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
1
System.out.println(System.getProperty("sun.boot.class.path"));
1
2
3
4
5
6
7
8
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/resources.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/rt.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jsse.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jce.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/charsets.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jfr.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/classes

**Extension ClassLoader(扩展类加载器):**负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的所有jar;

**App ClassLoader(扩展系统类加载器):**负责加载应用程序classpath目录下的所有jar和class文件


**CustomClassLoader:**自定义ClassLoader(继承java.lang.ClassLoader类/Extension ClassLoader/App ClassLoader);然而,不能继承Bootstrap ClassLoader,因为Bootstrap ClassLoader由底层C++编写,已迁入JVM内核,当JVM启动后,Bootstrap ClassLoader也随之启动,负责加载核心类库,并后遭Extension ClassLoader和App ClassLoader类加载器;

查看父类加载器

1
2
3
4
5
6
7
8
9
10
11
/**
* 查看父类加载器
*/
private static void test1() {
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类装载器:" + appClassLoader);
ClassLoader extensionClassLoader = appClassLoader.getParent();
System.out.println("系统类装载器的父类加载器——扩展类加载器:" + extensionClassLoader);
ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
System.out.println("扩展类加载器的父类加载器——引导类加载器:" + bootstrapClassLoader);
}

ExtensionClassLoader的parent为null因为bootstrapClassLoader不是一个普通的Java类

ClassLoader加载类的原理

原理

使用双亲委托模型来搜索类;每个ClassLoader实例都有一个父类加载器的引用(是一种包含关系);而虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有福类加载器,但可以用作其他的任务委托给它的福类加载器,这个过程是自上而下一次检查的;

首先有最顶层的类加载器Bootstrap ClassLoader视图加载,若没有加载到;则将任务转交给Extension ClassLoader试图加载,若没有加载到;则转交给App ClassLoader进行加载,若没有加载成功,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类;若都没有加载到这个类,则跳出ClassNotFoundException异常;否则将这个找到的类生成一个类的定义,并将它加载到内存中,最后返回这个类在内存中的实例对象;

为什么使用双亲委托模型

因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次;考虑到安全因素,若不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非改变JDK中ClassLoader搜索类的默认算法。

JVM搜索类的时候,如何判定两个class是相同的

JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的

自定义ClassLoader

三个重要方法

  • loadClassclassloader加载类的入口,此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从父ClassLoader中寻找,如仍然没找到,则从BootstrapClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法,如加载顺序相同,则可通过覆盖findClass来做特殊的处理,例如解密、固定路径寻找等,当通过整个寻找类的过程仍然未获取到Class对象时,则抛出ClassNotFoundException。如类需要resolve,则调用resolveClass进行链接。

  • findClass此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。

  • defineClass此方法负责将二进制的字节码转换为Class对象,这个方法对于自定义加载类而言非常重要,如二进制的字节码的格式不符合JVM Class文件的格式,抛出ClassFormatError;如需要生成的类名和二进制字节码中的不同,则抛出NoClassDefFoundError;如需要加载的class是受保护的、采用不同签名的或类名是以java.开头的,则抛出SecurityException;如需加载的class在此ClassLoader中已加载,则抛出LinkageError。

在自定义ClassLoader时,一般只重写findClass而不是loadClass

loadClass源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
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) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的判断方法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可

自定义ClassLoader

来自:http://blog.csdn.net/tonytfjing/article/details/47212291

1.定义一个Person接口

1
2
3
public interface Person {
public void say();
}

2.再定一个类实现这个接口

1
2
3
4
5
6
7
8
public class HighRichHandsome implements Person {

@Override
public void say() {
System.out.println("I don't care whether you are rich or not");
}

}

3.编写ClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{
/*
* 覆盖了父类的findClass,实现自定义的classloader
*/
@Override
public Class<?> findClass(String name) {
byte[] bt = loadClassData(name);
return defineClass(name, bt, 0, bt.length);
}

private byte[] loadClassData(String className) {
InputStream is = getClass().getClassLoader().getResourceAsStream(
className.replace(".", "/") + ".class");
ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
int len = 0;
try {
while ((len = is.read()) != -1) {
byteSt.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
return byteSt.toByteArray();
}
}

4.测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package classloader;

public class LoaderTest {
public static void main(String args[]) throws Exception{
test2();
}


private static void test2() throws Exception{
MyClassLoader loader = new MyClassLoader();
Class<?> c = loader.loadClass("classloader.HighRichHandsome");
System.out.println("Loaded by :" + c.getClassLoader());

Person p = (Person) c.newInstance();
p.say();

HighRichHandsome man = (HighRichHandsome) c.newInstance();
man.say();
}

private static void test3() throws Exception{
MyClassLoader loader = new MyClassLoader();
Class<?> c = loader.findClass("com.alibaba.classload.HighRichHandsome");
System.out.println("Loaded by :" + c.getClassLoader());

Person p = (Person) c.newInstance();
p.say();

//注释下面两行则不报错
HighRichHandsome man = (HighRichHandsome) c.newInstance();
man.say();
}
}

测试结果:

Loaded by :sun.misc.Launcher$AppClassLoader@73d16e93
I don’t care whether you are rich or not
I don’t care whether you are rich or not

遇到的一些问题

学习ClassLoader的教程是在网上看的;于是在使用sun.misc.Launcher的时候发现编译器找不到这个类;但是sun.misc这个包是存在于rt.jar的,只是apidoc中没有,那是因为sun开头的包不属于J2SE规范,是Sun公司的内部实现;
故,使用一下方法解决:

右键项目-》属性-》java bulid path-》jre System Library-》access rules-》resolution选择accessible,下面填上** 点击确定


参考资料:http://blog.csdn.net/xyang81/article/details/7292380
http://blog.csdn.net/tonytfjing/article/details/47212291