1、类加载器
定义一个Person类:
package cn.yishijie.config;
public class Person {
private int age;
private String name;
}
测试所有的类加载器:
package cn.yishijie.config;
public class Client {
public static void main(String[] args) {
ClassLoader classLoader = Person.class.getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
}
}
返回的结果分别为:
sun.misc.Launcher$AppClassLoader@58644d46
sun.misc.Launcher$ExtClassLoader@3b9a45b3
null
这个三个加载器是自带的加载器,分别是app类加载器,扩展类加载器,启动类加载器(是jvm的一部分,用c++写的,所以为null,没有对应的对象引用)
其中app类加载器,扩展类加载器这个两个类加载器都是Launcher类的内部类。这三个类加载都有自己负责加载的目录,分别有对应的系统变量设置。这里只是截取部分的源码:
App类加载器
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
....
扩展类加载器
static class ExtClassLoader extends URLClassLoader {
...
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
...
启动类加载器
private static String bootClassPath = System.getProperty("sun.boot.class.path");
通过下面的属性获取来获取对应的加载路径:
System.getProperty("java.class.path");
System.getProperty("sun.boot.class.path");
System.getProperty("java.ext.dirs");
上述的信息都是用分号隔开的,可以通过下面打印:
private void print(String path){
String[] split = path.split(";");
for (String s : split) {
System.out.println(s);
}
}
2、类型的加载
对类的首次主动使用,会触发我们类的初始化,主动方式包括下面:
访问静态属性:getStaticField
调用静态方法:invokeStaticMethod
反射调用:Class.forName
使用main方法入口:main
创建对象:new
初始化子类主动使用,会触发父类初始化
双亲委托机制:
将加载的动作优先交给父加载器加载,当父加载器不能加载的时候,子加载器才去加载,子加载器也加载不到的话,就抛出ClasssNotFoundException。
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;
}
}
首先App类加载器查看是否已经加载,没有就扩展类加载查看是否已经加载,没有就查看启动类加载器是否已经加载,没有的话,启动类加载器就尝试在自己加载的路径下尝试加载,加载不到的话,就扩展类尝试加载,再加载不到的话,就App类加载器尝试加载,如果都找不到,那么就抛出ClassNotFoundException。
直接将对应的class文件删除掉,假设编译能通过,通过new触发加载的动作,如果加载不到class文件的话,会抛出:Exception in thread "main" java.lang.NoClassDefFoundError
这个异常是因为在需要加载的路径下,找不到需要的class文件或者找的class文件是异常的导致的。class文件异常的情况下,一般是因为静态属性初始化的时候出问题了,ExceptionInInitializerError异常会出现一次,以后再次触发加载动作的时候,就会抛出:Exception in thread "main" java.lang.NoClassDefFoundError异常。
如果通过:Class.forName("cn.yishijie.config.Person")方式:会抛出:ClassNotFoundException异常
自定义类加载器:
官方给的模板方式:
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
}
}
可以看出,重写findClass即可
package cn.yishijie.config;
import java.io.*;
public class MyClassLoader extends ClassLoader {
// 对应的class文件的路径
String path = "D:\\jeffchan\\workspace\\hello\\target\\classes\\";
// 用于存对应的class文件的大小
private int size;
@Override
public Class findClass(String name) {
byte[] b = loadClassData(path,name);
return defineClass(name, b, 0, size);
}
private byte[] loadClassData(String path, String name){
String s = name.replaceAll("\\.", "/");
File file = new File(path+s+".class");
try {
InputStream inputStream = new FileInputStream(file);
byte[] b = new byte[1024 * 1024 * 10];
this.size = inputStream.read(b);
return b;
}catch (Exception e){
;
}
return null;
}
}
测试:
package cn.yishijie.config;
import java.lang.reflect.Field;
public class Client {
public static void main(String[] args) throws Exception{
MyClassLoader myClassLoader = new MyClassLoader();
Class aClass = myClassLoader.findClass("cn.yishijie.config.Person");
Object o = aClass.newInstance();
Field field = aClass.getDeclaredField("name");
field.setAccessible(true);
field.set(o, "jeffchan");
System.out.println(o);
System.out.println(aClass.getClassLoader());
}
}
打印出结果:
Person{age=0, name='jeffchan'}
cn.yishijie.config.MyClassLoader@7699a589
自定义的类加载器,可以对class文件进行一些加密等操作,还可以从自己指定的路径下获取class,比如存在任意一个磁盘,或者存到数据库等位置,还有官方给的通过网络的形式将class文件读入进来,都可以通过自定义的类加载器将类加载进来。