1、基础理论扩展
- Java的生命周期由加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。其中验证、准备、解析三个部分又统称为连接(Linking)
- javac编译器程序入口:com.sun.tools.javac.Main类中的main()方法。
- 类加载器:Bootstrap ClassLoader引导类/启动类加载器(/jre/lib 目录中,由C++编写)—》Extension ClassLoader扩展类加载器(jre/lib/ext,由Java 实现getLauncher())—》Application ClassLoader应用程序类加载器(CLASSPATH 目录的所有类库,由Java实现getClassLoader())—》自定义加载器。
- 类加载器的过程:加载(找文件)—》验证(验证字节码文件)—》准备(静态变量分配内存,并赋予默认值)—》解析(将符号引用替换为直接引用)—》初始化(对静态变量初始化为指定值,执行静态代码块)
- 类加载器的机制:双亲委派机制。通俗的说由子类逐级委派向上级加载。如果加载失败,再由顶级向下逐级加载,如果都失败,再由自己当前类加载器加载自己。
- 双亲委派机制优点:、sun.misc.Launcher->java.lang.ClassLoader
1、沙箱安全机制:自己写的jdk内部类,是不会被加载的,防止核心API库被随意篡改
2、避免类的重复加载:当父类已经加载了该类时,子类的ClassLoader不会重复加载,保证被加载类的唯一性。
代码编写
public class TestJdkClassLoader {
public static void main(String[] args) {
System.out.println("/jre/lib 目录中的类加载器:"+String.class.getClassLoader());
System.out.println("jre/lib/ext 目录中的类加载器:"+CurrencyNames_be_BY.class.getClassLoader());
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println("测试当前类 加载器:"+classLoader);
System.out.println("当前类的父类加载器:"+classLoader.getParent());
System.out.println("当前类父类的父类加载器:"+classLoader.getParent().getParent());
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
System.out.println("启动类加载器加载的jar包:"+JSON.toJSONString(urLs));
System.out.println("扩展类加载器加载的jar包:"+System.getProperty("java.ext.dirs"));
System.out.println("应用类加载器加载的jar包:"+System.getProperty("java.class.path"));
}
}
执行结果
这里由于类加载器的双亲委派机制,应用类里面会读取打印jre/lib.并不是说应用类加载器加载了lib包的jra包。
编写自定义加载器
public class Test0002 {
static class MyClassLoader extends ClassLoader {
private String classPath;
MyClassLoader(String classPath){
this.classPath = classPath;
}
protected byte[] loadByte(String fileName) throws Exception {
fileName = fileName.replaceAll("\\.","/");
FileInputStream inputStream = new FileInputStream(classPath +"/"+ fileName+".class");
int len = inputStream.available();
byte [] data = new byte[len];
inputStream.read(data);
inputStream.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte [] data = loadByte(name);
return defineClass(name,data,0,data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
protected Class<?> loadClass2(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();
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;
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
@Override
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();
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
if(!name.startsWith("com.jackrain.nea.ac.tool")){
c = this.getParent().loadClass(name);
}else {
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;
}
}
}
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader("F:\\TestJvmCode");
Class aClass = myClassLoader.loadClass("com.jackrain.nea.ac.tool.Test0001");
Object object = aClass.newInstance();
Method method = object.getClass().getDeclaredMethod("sout",null);
method.invoke(object,null);
System.out.println(aClass.getClassLoader().getClass().getName());
}
}
思路:跟一边源码后可以发现类加载器最核心的类,java.lang.ClassLoader类,该类有俩个核心方法,一个是loadClass(String,bolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以自定义类加载器主要是重写findClass方法。findBootstrapClassOrNull(这个是C++本地方法,启动类加载类的方法)
这里遇到了两个问题:因为是自己写的类加载器只加载了Test0001,但是Test0001的父类是Object 会出现下面这个问题:
当我把这个Object.class,copy放到这个路径下,又报错:
看到这里才让我真正明天为什么一开始学习Java的时候,为什么定义类路径和名称的时候不能和jdk自带的路径一样了,这原来是触发了类加载器的沙箱安全机制。也就是说jdk自带的内部类只能由启动类加载器加载。优化思路:既然如此那我们修改一下,自己定义的类加载只加载自己固定路径下的包,其它的还有启动类加载。