目录
文章摘自:深入理解Java虚拟机 第二版 周志明著
本文主要讲解"类加载器"。
-
何为类加载器
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类二进制字节流”这个动作放刀Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为”类加载器“。
-
类与类加载器
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。
对于任意一个类,都需要由它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。即判断两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Calss文件,被同一个虚拟机加载,只要它们类加载器不同,那这两个类就不相等。这说的相等包括代表类的Calss对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,亦包括instanceof关键字做对象所属关系判定等情况。
package com.gary.test.classloader;
import java.io.IOException;
import java.io.InputStream;
public class ClassloaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("com.gary.test.classloader.ClassloaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.gary.test.classloader.ClassloaderTest);
}
}
结果是
class com.gary.test.classloader.ClassloaderTest
false
可以看出我们使用这个类加载器加载得到的类,并实例化这个类的对象,但是这个对象与与类com.gary.test.classloader.ClassloaderTest做所属类型检查的时候却返回了false。即两个CalssLoaderTest类,一个由系统应用程序类加载器加载的,另外一个是由我们自定义的类加载器加载的,虽然都是来自同一个Class文件,但是依然是两个独立的类。
-
类加载器的双亲委派模型
启动类加载器(
Bootstrap ClassLoader
):由C++
语言实现(针对HotSpot
),负责将存放在<JAVA_HOME>\lib
目录或-Xbootclasspath
参数指定的路径中的类库加载到内存中。其他类加载器:由
Java
语言实现,继承自抽象类ClassLoader
。如:扩展类加载器(
Extension ClassLoader
):负责加载<JAVA_HOME>\lib\ext
目录或java.ext.dirs
系统变量指定的路径中的所有类库。应用程序类加载器(
Application ClassLoader
)。负责加载用户类路径(classpath
)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
如图下所示:
这并不是一个强制性的请求,而是Java设计者推荐给开发者的一种类加载器实现方式。
双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的父类加载器中,只有当父加载器反馈自己无法完成这个加载请求时候,子加载器才回尝试自己去加载。
-
双亲委派模型好处
从其工作过程可以看出一个显而易见的好处:Java类随着它的类加载器一起具备了一种优先级的层次关系。如java.lang.Object,它存放在rt.jar,无论哪一个类加载器yao加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,用户如果自己编写了一个称为java.lang.Object的类,并放在程序的CalssPath中,那系统中将会出现多个不同的Object类,Java体系中最基础的行为也就无法保证了。
-
双亲委派模型是如何实现的?
实现双亲委派的代码集中在java.lang.ClaassLaoder的loadCalss方法中。如下图所示为jdk1.8中代码:
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;
}
}
可以看出:先查是否已经被加载过,若没有加载则调用父加载器的loadCalss方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass方法进行类加载。
System.nanoTime();可用于计时,其返回值是从确定的值算起的,但是值任意,可能是一个未来的时间,所以返回的值可能为负数。
可以看出:想要定义类加载器,就需要重写findClass
方法。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
可以看出:我们需要在findCalss方法中实现将一个指定类名称转为Class对象。
如果读取一个指定的名称的类为字节数组的话,可以用defineCalss方法把字节数组转为Calss对象。
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
一个例子:
package com.gary.test.classloader;
public class TestForClssLoder {
public void hello() {
System.out.println("我是由:" +
getClass().getClassLoader().getClass() + "加载的");
}
}
package com.gary.test.classloader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
public class Test {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader() {
}
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
String filename = classPath + "/" + name.replaceAll("\\.", "/") + ".class";
FileInputStream fis = new FileInputStream(filename);
int len = fis.available();
byte[] b = new byte[len];
fis.read(b);
fis.close();
return defineClass(name, b, 0, b.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader();
Class<?> clazz = classLoader.loadClass("com.gary.test.classloader.TestForClssLoder");
Object object = clazz.newInstance();
Method method = clazz.getDeclaredMethod("hello");
method.invoke(object);
}
}
}
我是由:class sun.misc.Launcher$AppClassLoader加载的
根据双亲委派模型可知通过sun.misc.Launcher$AppClassLoader
类加载器(Application ClassLoader)加载。
为了让我们自定义的类加载器加载,我们把TestForClssLoder.class文件放入到其他目录,并删除bin目录下的TestForClssLoder.class。
package com.gary.test.classloader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
public class Test {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader() {
}
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
String filename = classPath + "/" + name.replaceAll("\\.", "/") + ".class";
FileInputStream fis = new FileInputStream(filename);
int len = fis.available();
byte[] b = new byte[len];
fis.read(b);
fis.close();
return defineClass(name, b, 0, b.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class<?> clazz = classLoader.loadClass("com.gary.test.classloader.TestForClssLoder");
Object object = clazz.newInstance();
Method method = clazz.getDeclaredMethod("hello");
method.invoke(object);
}
}
}
我是由:class com.gary.test.classloader.Test$MyClassLoader加载的
成功。