JVM类加载器
类加载器是什么
类加载阶段中的 “通过一个类的全限定名来获取描述此类的二进制字节流” 这个动作在JVM外部实现,有利于让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块就称为“类加载器”。
类加载器的作用
1.实现类的加载过程中加载这个动作
2.确保这个被加载的类在JVM中的唯一性
注意:只有同一个类被同一个类加载器加载,这两个被加载的类才相等,也就是只有这样它们的equals,isAssignableFrom和isInstance方法返回的结果才为true。
类加载器的分类
-
JVM的角度:
- 启动类加载器,使用C++实现,是虚拟机的一部分
- 其他的类加载器,由Java实现,在虚拟机外部,全都继承自抽象类java.lang.ClassLoader
-
java程序猿角度:
-
启动(引导)类加载器,是最顶层的类加载器,java无法直接引用此类加载器。负责加载<JAVA_HOME>\lib目录下的,或-Xbootclasspath参数指定的路径中的并且是虚拟机识别的类库。
注意:在编写自定义类加载器时,若需把加载请求委派给引导类加载器,直接使用null代替即可,如下java.lang.ClassLoader.getClassLoader()方法所示:
public ClassLoader getClassLoader() { ClassLoader cl = getClassLoader0(); if (cl == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass()); } return cl; }
这里也侧面说明:当通过getClassLoader方法来获取一个类的类加载器时,如果它返回的是null,表示加载它的类加载器是启动类加载器,而不是没有类加载器来加载它。
-
扩展类加载器,主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的所有类库。开发者可以直接使用扩展类加载器。
-
应用程序(系统)类加载器,负责加载当前用户路径classpath下指定的类库。如果程序中没有自定义类加载器,通常默认使用这个类加载器。
-
双亲委派模型
1.双亲委派模型的实现
实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass方法中,如下所示:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
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);
// 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;
}
}
将上面的代码转化为下面具体的图片更容易理解,左边的文字应该改为:向上询问父类是否类已经加载,右边的文字改为:向下尝试让子类加载类
2.双亲委派模型的优点
- Java类随着它的类加载器一起具备了一种带优先级的层次关系
- 可以保证java程序稳定运行,防止重复加载一个类。
例如,无论哪一个类加载器要加载 java.lang.Object这个类,最终需委派给模型顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。若没有使用双亲委派模型,由各个类加载器自行去加载,用户编写了一个java.lang.Object的类放在ClassPath中,那系统中将出现多个不同的Object类,Java体系中最基础的行为就无法保证,应用程序将变得一片混乱。
自定义类加载器
自定义类加载器主要是通过继承自ClassLoader类来实现的,JDK1.2后推荐重写findClass方法而不是loadClass方法,因为这样可以保证双亲委派模型不被破坏。
public class MyClassLoaderTest {
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> clazz = myClassLoader.loadClass("practice.leetcode.offer.TestClass"); //这里还是调用loadClass方法,传入全类名作为参数
TestClass testClass = (TestClass) clazz.newInstance();
testClass.sayHello();
}
}
class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = null;
//下面是测试类编译后的字节码文件存储路径
String path = "C:\\Users\\腾腾娃发光的板砖\\Desktop\\java\\out\\production\\java\\";
byte[] arr = new byte[0];
try {
name = name.replace(".", "//");
FileInputStream is = new FileInputStream(new File(path + name + ".class"));
arr = new byte[is.available()];
is.read(arr);
is.close();
} catch (Exception e) {
e.printStackTrace();
}
defineClass(className, arr, 0, arr.length);
return null;
}
}
//测试类
public class TestClass {
public void sayHello() {
System.out.println("你好,自定义类加载器~");
}
}
//测试结果:
你好,自定义类加载器~
参考资料
《深度理解java虚拟机》