classloader
类加载器就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。
class字节码
javap -p main.class
class加载过程
loading=> 加载Class对象到JVM, Class<?> clazz = ?- .java=>.class; 存储到byte[]
- bytes[] => Class<?> clz= 加载到jvm
Link,校验Class- 验证:java语言规范
- 准备:==>(为
static修饰的成员变量分配内存,并设置jvm指定的默认值) - 解析:
intializestatic代码块加载,并给static属性赋值.- 构造函数,分配heap空间
- Class.forName(name,true); true就是指定获取class是否初始化
- using
- unloading(代码实现)=>PG做GC
// 不会初始化操作,只是加载class,默认不执行Link,看源码
// 自己实现loadclass时就只是load
Class<?> clazz = Classloader.loadClass();
// initilize = false不初始化,默认初始化-true,
Class.forName();
static执行的阶段
// 1. 准备阶段(link):a分配内存,默认复制=0
// 2. 初始化阶段(init): 复制=1
private static int a = 1;
5个阶段,intinalize阶段,Class.forName默认是执行的。
双亲委派
-
父类加载器并不是通过继承关系来实现的,而是采用组合实现的
-
jvm启动时通过启动类加载其他加载器,再通过其他加载器加载class
-
Bootstrap ClassLoader(启动类加载器)
- JAVA_HOME\lib目录
- -Xbootclasspath 指定
- C++实现native
-
Extension ClassLoader(扩展类加载)
- JAVA_HOME\lib\ext
- 系统变量java.ext.dirs=
- java实现
-
Application ClassLoader(应用类加载器)
- 加载classpath=
加载类的3个方式
java -classpath=Classloader.loadClass只加载Class.forName加载 + 初始化
classloader加载流程(委托给父类去加载:通过组合实现的)
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载.
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载。
这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。
尤其是在需要运行时加载的长场景Thread.currentThread().getContextClassLoader();保证同一个对象。
自己实现类加载
ClassLoader源码解读
1.loadclass
loadclass()-主加载方法,加载Class到内存
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;
}
}
- 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
- 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false).没有调用bootstrap类加载器来加载。
- 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。
2.findclass
findclass() - 查找Class对象,通过类名
protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);}
-
抽象类ClassLoader的findClass函数默认是抛出异常的需要被overwrite.而前面我们知道,loadClass在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的findClass函数,因此我们必须要在loadClass这个函数里面实现将一个指定类名称转换为Class对象. -
Java提供了defineClass方法,通过这个方法,就可以把一个字节数组转为Class对象
- 通过编译java源文件生成字节码
- 通过加载class字节码生成class对象
3.defineClass
defineClass() - 通过字节码定义Class,String->字节码Class
protected final Class<?> defineClass(String name, byte[] b, int off, int len)throws ClassFormatError {
return defineClass(name, b, off, len, null);
}
自定义实现的Classloader
//自定义类加载器
public class MyClassLoader extends ClassLoader {
//加载当前目前目录下的class文件,.class文件的目录
private String classLocation;
public MyClassLoader(ClassLoader parent, String classLocation) {
super(parent);
this.classLocation = classLocation;
}
//从classLocation加载.class文件
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByteFromClass(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
throw new ClassNotFoundException();
}
}
//读取文件的字节码 .class
private byte[] loadByteFromClass(String clsName) throws IOException {
clsName = clsName.replaceAll("\\.", "/");
FileInputStream fis = null;
try {
fis = new FileInputStream(classLocation.concat("/").concat(clsName).concat(".class"));
// 估计读取文件的大小
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
return data;
} finally {
fis.close();
}
}
public static void main(String[] args) {
MyClassLoader myClassLoader = new MyClassLoader(getSystemClassLoader(), "/Users/a002");
try {
//实际系统的目录
//Users/a002/com/cook/code/javax/Test.class
Class clazz = myClassLoader.loadClass("com.cook.code.javax.Test");
Object obj = clazz.newInstance();
Method testMethod = clazz.getDeclaredMethod("test", null);
testMethod.invoke(obj, null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
OUT:
Test加载器: class com.cook.code.javax.launcher.MyClassLoader
Test父类加载器:(JDK默认类加载器) sun.misc.Launcher$AppClassLoader@14dad5dc
*/
OSGI(open service Gateway intiative)
面向服务的模块化热插拔功能,动态加载服务的实现
JDK-SPI
参考Java-SPI.md文件
ps
class.forname() 与classload.loadclass()的区别
- class.forname 能初始化,load,verify,link,init
- 与classload.loadclass:只能到load,verify,link
问什么双亲委托
String有final修饰,可以不被继承,不被修改; 自定义classLoader可以加载自己的String(跳过final), 双亲委派保证优先加载系统的String。