1.类的加载过程
当我们在使用某个对象时,jvm需要先将class文件加载进内存,大改款流程如下:
加载:将硬盘上的class文件读到内存生成一个Class对象,这一步通常是在使用到这个类时 才进行加载,属于懒加载
验证:校验class字节码文件是否符合java规范
准备:给静态变量分配内存,进行初始化,这里的初始化是指按照jvm的规范赋默认值,并不是类中定义的值
解析:将class文件的符号引用替换为直接引用,符号引用指的是我们在类中写的各种关键字,替换为内存地址,也就是静态链接
初始化:对静态变量赋值,执行静态代码块
类加载到方法区后,会创建一个Class对象放到堆区(heap)
2.类加载器和双亲委派机制
引导类加载器(BootstrapClassloader):负责加载jre下lib包下的核心类库,如rt.jar等
扩展类加载器(ExtClassloader):用于加载jre下lib/ext下的类库
应用类加载器(AppClassloader):用于加载我们自己的应用类
直接上代码:
java的两个类加载器都是Launcher.class的内部类,都继承URLClassLoader
在创建Launcher对象时,会初始化AppClassLoader对象,并且指定parent为ExtClassLoader
经过一系列的点点后到了这里,parent=ExtClassLoader
2.1 类加载器的源码
入口是执行AppClassLoader的loadclass方法,最终都会执行到其父类的loadclass方法
在这里首先会从AppClassLoader拿一次看有没有加载过,如果加载了就直接返回,如果没有则调用parent再次执行loadclass方法,父类也没有则调用findBootstrapClassorNull去获取。缓存中没有加载的话就再从父到子去加载。流程图也如下:
2.2 打破双亲委派机制
要打破双亲委派机制就要重写loadclass方法,可以模仿Tomcat的写法,重写findclass的逻辑,自己读取class文件然后传到defindClass方法,这样就完成了自定义类加载器
package com.test;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyClassLoader extends ClassLoader{
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
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(!name.startsWith("com.classload")){
c=getParent().loadClass(name);
}else {
c = findClass(name);
}
long t1 = System.nanoTime();
// 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;
}
}
protected Class<?> findClass(final String name) {
byte[] b = new byte[0];
try {
b = loadByte(name);
} catch (Exception e) {
e.printStackTrace();
}
return defineClass(name, b, 0, b.length);
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader("/Users/ligang/Desktop/code/test");
Class<?> aClass = myClassLoader.loadClass("com.classload.User");
Object o = aClass.newInstance();
Method print = aClass.getDeclaredMethod("print", null);
print.setAccessible(true);
print.invoke(o,null);
System.out.println(aClass.getClassLoader().getClass().getName());
}
}
执行结果:
Connected to the target VM, address: '127.0.0.1:50906', transport: 'socket'
加载成功
com.test.MyClassLoader
Disconnected from the target VM, address: '127.0.0.1:50906', transport: 'socket'
Process finished with exit code 0