The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a “parent” class loader. When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself.
“双亲” 原文是 parent delegate,在 Java 文档原文的意思:将加载类的任务委派给上一级类加载器,而双亲的翻译却让人摸不着头脑。
public abstract class ClassLoader {
上级类加载器
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve) {
1、检查是否加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
2.1 递归委派给上级类加载器
c = parent.loadClass(name, false);
} else {
2.2 委派给顶级启动类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
2.3 上级类加载器无法加载,抛出异常
}
3. 交给自己加载,findClass() 我后文再说
if (c == null) {
c = findClass(name);
}
}
return c;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
}
Class.forName
Class.forName(xxx.xx.xx)返回的是一个类
Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,
也就是说JVM会执行该类的静态代码段你要记住一个概念,静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了。而且以后不会再走这段静态代码了。
public class StaticClass {
static {
System.out.println("被加载啦");
}
public StaticClass (){
System.out.println("构造方法");
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class myStatic = Class.forName("com.class_load.StaticClass");
myStatic.newInstance();
Class myStatic1 = Class.forName("com.class_load.StaticClass");
myStatic1.newInstance();
Class myStatic2 = StaticClass.class;
myStatic2.newInstance();
}
}
输出是:
被加载啦
构造方法
构造方法
构造方法
public class Main {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class myStatic1 = Class.forName("com.class_load.StaticClass");
}
}
输出:
被加载啦
public class Main {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class myStatic = StaticClass.class;
}
}
执行后是没有任何输出的。
Java 还提供了另一种方法来生成对 Class 对象的引用,即使用类字面常量。形如:xxx.class 的形式。但是这种形式创建 Class 对象引用时,不会自动初始化 Class 对象。初始化被延迟到了对静态方法或者非常数静态域首次引用时才执行。
public class Initable {
static final int staticFinal = 47;
static final int staticFianl2 = ClassInitialization.rand.nextInt(1000);
static {
System.out.println("Initializing Initable");
}
}
public class Initable2 {
static int staticNonFinal = 147;
static {
System.out.println("Initializing Initable2");
}
}
public class Initable3 {
static int staticNonFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws ClassNotFoundException {
Class initable = Initable.class;
System.out.println("after creating Initable ref");
//Does not trigger initialization
System.out.println(Initable.staticFinal);
//Does trigger initialization
System.out.println(Initable.staticFianl2);
//Does trigger initialization
System.out.println(Initable2.staticNonFinal);
Class initable3 = Class.forName("chapter13.t2.Initable3");
System.out.println("after creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}
执行输出结果:
after creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
after creating Initable3 ref
74
从对 initable 引用的创建中可以看到,仅使用 .class 语法来获取对类的引用不会发生初始化。但是 Class.forName() 立即就进行了初始化。如果一个 static final 值是“编译期常量”,就像 Initable.staticFinal 那样,那么这个值不需要对 Initable 类进行初始化就可以被读取。但是如果只是将一个域设置为 static 和 final 的,还不足以确保这种行为,例如 Initable.staticFianl2 的访问将强制进行类型的初始化,因为他不是一个编译器常量。
classLoader.load 没有执行初始化
Class.forName(className)装载的class已经被实例化,classLoader.loadClass().则只是将信息装载给JVM。在JDBC中 Class.forName(“com.mysql.jdbc.Driver”),如果换成getClass().getClassLoader().loadClass(“com.mysql.jdbc.Driver”),就不可以,因为它只是想JVM装载了Driver的类信息,但是没有实例化,也就不能执行相应的操作,因为Driver是需要被初始化才能被使用的。
public class Main {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
currentClassLoader.loadClass("com.class_load.StaticClass");
}
}
没有任何输出。
三个默认ClassLoader
public class Test {
public static void main(String [] args) {
ClassLoader cl = Test.class.getClassLoader();
while (cl != null) {
System.out.println(cl + "'s parent ClassLoader is " + cl.getParent());
cl = cl.getParent();
}
}
}
返回结果:
sun.misc.Launcher$AppClassLoader@18b4aac2's parent ClassLoader is sun.misc.Launcher$ExtClassLoader@4617c264
sun.misc.Launcher$ExtClassLoader@4617c264's parent ClassLoader is null
1. Bootstrap ClassLoader
Bootstrap ClassLoader称为启动类加载器,是Java类加载层次中最顶级的加载,负责加载JDK中的核心类库,如:rt.jar、resource.jar、charsets.jar等,Bootstrap类加载器的加载目录由系统属性(sun.boot.class.path)指定,目录如下:
import java.net.URL;
public class Test {
public static void main(String [] args) {
// 也可通过 System.getProperty("sun.boot.class.path" 获取
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
}
}
本机JDK环境返回结果:
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/classes
2. Extension ClassLoader
Extension ClassLoader称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的所有文件。Extension ClassLoader是JVM启动类(sun.misc.Launcher)的内部类,继承自java.net.URLClassLoader类。其加载目录由系统属性(java.ext.dirs)指定。本机加载目录如下:
public class Test {
public static void main(String [] args) {
String paths = System.getProperty("java.ext.dirs");
String [] pathList = paths.split(File.pathSeparator);
for(String path : pathList) {
System.out.println(path);
}
}
}
本机返回结果:
/Users/Jerry/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
3.App ClassLoader
App ClassLoader称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。App ClassLoader既是加载系统属性(java.class.path)的类加载器,加载目录如下:
package test;
import java.io.File;
public class Test {
public static void main(String [] args) {
String paths = System.getProperty("java.class.path");
String [] pathList = paths.split(File.pathSeparator);
for(String path : pathList) {
System.out.println(path);
}
}
}
本机命令行编译并执行:java -cp /Users/Jerry/documents:/Users/Jerry/documents/workspace/xf-test test.Test
返回结果:
/Users/Jerry/documents
/Users/Jerry/documents/workspace/xf-test
这里,如果不指定classpath,本地执行返回当前目录(”.”),如果通过IDE执行,则会返回IDE设定的classpath值
4.自定义ClassLoader
除了Java默认提供的三种ClassLoader外,用户也可以根据自己需要,通过继承ClassLoader超类定义属于自己的ClassLoader。范例如下:
定义自己的ClassLoader类,当通过ClassLoader.loadClass()方法加载类时,如果加载不到对应的类,则会调用自定义ClassLoader的findClass方法继续加载类,详见下一节:
public class MyClassLoader extends ClassLoader{
public static final String driver = "/Users/Jerry/Desktop/";
public static final String fileTyep = ".class";
public Class findClass(String name) {
byte[] data = loadClassData(name);
return defineClass(data, 0, data.length);
}
public byte[] loadClassData(String name) {
FileInputStream fis = null;
byte[] data = null;
try {
File file = new File(driver + name + fileTyep);
System.out.println(file.getAbsolutePath());
fis = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int ch = 0;
while ((ch = fis.read()) != -1) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
System.out.println("loadClassData-IOException");
}
return data;
}
}
编写测试类,编译,并将class文件移动到在MyClassLoade的driver目录下:
package test;
public class Test {
public void sayHello(String name) {
System.out.println("hello " + name);
}
测试MyClassLoader:
public class Test {
public static void main(String[] args) {
MyClassLoader cl1 = new MyClassLoader();
try {
Class c1 = cl1.findClass("Test"); // 或调用:Class c1 = cl1.loadClass("Test");
Object object = c1.newInstance();
Method method = c1.getMethod("sayHello", String.class);
method.invoke(object, "Jerry");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
返回结果:
/Users/Jerry/Desktop/Test.class
hello Jerry
【以上实验,均已经验证过】
参考文章:
1、https://blog.csdn.net/u010841296/article/details/89731566
2、https://www.jianshu.com/p/f4318e626a22
3、https://www.runoob.com/w3cnote/java-class-forname.html
4、https://blog.csdn.net/syilt/article/details/90706332
5、https://blog.csdn.net/jiamingku/article/details/82415056