Java--ClassLoader解析
2019-04-03 本文已影响0人
二妹是只猫
ClassLoader
类从编译到执行的过程
- 编译器将Robot.java源文件编译为Robot.class字节码文件
- ClassLoader将字节码转换成JVM中的Class<Robot>对象
- JVM利用Class<Robot>对象实例化为Robot对象
定义: ClassLoader在java中有着非常重要的作用,它主要工作在Class装载的加载阶段,主要作用是从系统外部获得Class二进制数据流。它是java的核心组件,所有的class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给java虚拟机(JVM)进行连接、初始化等操作
种类
- BootStrapClassLoader:C++编写,加载核心库java.*
- ExtClassLoader:java编写,加载扩展库javax.*
- AppClassLoader: java编写,加载程序所在目录(我们自己编写的代码)
- 自定义ClassLoader: java编写,定制化加载
实现自定义ClassLoader:
注意:使用defineClass生成Class文件,重写findClass制定加载class的路径
- 首先使用javac生成一个Wali.class
public Class Wali{
static{
System.out.println("hello Wali");
}
}
- 编写自定义ClassLoader:
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;
public MyClassLoader(String path, String classLoaderName) {
this.path = path;
this.classLoaderName = classLoaderName;
}
//用于寻找类文件
@Override
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//用于加载类文件
private byte[] loadClassData(String name) {
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
while ((i = in.read()) != -1) {
out.write(i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return out.toByteArray();
}
}
- 运行:
public class ClassLoaderChecker {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader m = new MyClassLoader("/Users/Desktop/", "myClassLoader");
Class c = m.loadClass("Wali");
System.out.println(c.getClassLoader());
System.out.println(c.getClassLoader().getParent());
System.out.println(c.getClassLoader().getParent().getParent());
System.out.println(c.getClassLoader().getParent().getParent().getParent());
c.newInstance();
}
}
自定义ClassLoader
双亲委派机制
双亲委派机制定义:
首先自底向上查找class是否已经被装载了,若已加载直接返回,否则向上继续查找,若找到最顶层BoostrapClassLoader依然没有发现,说明该class没有被装载进内存成为Class对象,则需要自顶向下的尝试装载该class,若能装载成功则直接返回,否则依次向下寻找,直到最底层依然不能装载则抛出异常,
代码:ClassLoader
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);
1 if (c == null) {
long t0 = System.nanoTime();
try {
2 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;
}
}
当加载字节码文件时,首先会加上同步锁防止别的类加载器去加载,在标记1处判断当前类加载器是否加载过,标记2处如果父类加载器不为空,调用父类加载器的loadClass方法迭代排查是否加载过。 这就符合了上面定义对双亲委派机制的介绍,从上方图片自定义ClassLoader中也能看到它们是这样的关系
为什么要使用双亲委派机制去加载类
避免同个字节码重复加载,浪费内存
类的加载方式
- 隐式加载:new
- 显示加载:loadClass、forName等
类的装载(加载)过程如下图:
loadClass和forName的区别
- Class.forName得到的class是已经初始化完成了的 (MySQL加载驱动时,需要调用静态代码块完成一些操作)
- ClassLoader.loadClass得到的class是还没有链接的。(用于Spring IoC中的延迟加载机制)
代码验证上面结论
- 用来加载的类:
public class Robot {
static {
System.out.println("Hello Robot");
}
}
- 使用loadClass加载:
public class LoadDifference {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader cl = Robot.class.getClassLoader();
}
}
loadClass
结论:没有执行静态代码块的代码,符合结论
- 使用forName:
public class LoadDifference {
public static void main(String[] args) throws ClassNotFoundException {
Class r = Class.forName("com.interview.javabasic.reflect.Robot");
}
}
forName