.java文件 编译后,得到.class文件,JVM 需要 读取这些.class文件才能后运行。
那么,如何读取.class文件呢?这就是 类加载器需要解决的问题。
JDK提供的:java.lang.ClassLoader 是一个抽象类
实现ClassLoader的子类,以扩展Java虚拟机动态加载类的方式
ClassLoader类使用委托模型来搜索类和资源:
类加载器的每个实例都有一个关联的父类加载器。
当请求查找类或资源时,类加载器实例将在尝试查找类或资源本身之前,将对该类或资源的搜索委托给其父类加载器。
虚拟机的内置类加载器称为“引导类加载器”,它本身没有父类,但可以作为类加载器实例的父类。
Java虚拟机以依赖于平台的方式从本地文件系统加载类。例如,在UNIX系统上,虚拟机从CLASSPATH环境变量定义的目录加载类。但是,有些类可能不是源于文件;它们可能来自其他来源,如网络,也可能由应用程序构建
defineClass方法将字节数组转换为类的实例
为了确定引用的类,Java虚拟机调用最初创建该类的类加载器的loadClass方法。
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
. . .
}
}
// 父类加载器
private final ClassLoader parent;
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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;
}
}
// 类加载器,必须实现该方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
示例,
public class MyClassLoader extends ClassLoader{
public static void main(String[] args) throws Exception{
Thread mc = new Thread(() -> {
People people = new People();
// People,这样是用 sun.misc.Launcher$AppClassLoader@18b4aac2 加载
System.out.printf("people getClassLoader "+people.getClass().getClassLoader());
try {
// 获取当前线程的 类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> peopleClass = Class.forName("wxj.test.mclassloader.People", true, classLoader);
// peopleClass getClassLoader wxj.test.mclassloader.MyClassLoader@65fc50e4
System.out.printf("peopleClass getClassLoader " + peopleClass.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}, "mc");
// 设置当前线程的 类加载器
mc.setContextClassLoader(new MyClassLoader());
mc.start();
while (true){}
}
public MyClassLoader(ClassLoader parent) {
super(parent);
}
public MyClassLoader() {
}
/**
* 类加载器的 入口
* 如果自定义类加载器,重写了 改方法,那么 就会走自己定义的 加载规则
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("MyClassLoader.loadClass:exe...");
return super.loadClass(name);
}
/**
* 如果自定义类加载器重写了 loadClass 方法,那么 可以 实现 findClass,也可以不实现,因为 加载规则是自己定义的
* 如果自定义类加载器没有重新 loadClass方法,根据双亲委派,父类加载器加载失败时,会调用 findClass 加载
*/
protected Class<?> findClass(String name) throws ClassNotFoundException
{
System.out.println("MyClassLoader.findClass:exe...");
String classPath = "D:\\wxjwork\\code\\testv0\\cal\\target\\classes\\wxj\\test\\mclassloader";
File file = new File(classPath + "\\People.class");
try{
byte[] bytes = getClassBytes(file);
//defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
private byte[] getClassBytes(File file) throws Exception
{
// 这里要读入.class的字节,因此要使用字节流
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel wbc = Channels.newChannel(baos);
ByteBuffer by = ByteBuffer.allocate(1024);
while (true){
int i = fc.read(by);
if (i == 0 || i == -1)
break;
by.flip();
wbc.write(by);
by.clear();
}
fis.close();
return baos.toByteArray();
}
}