Java 类加载机制
1、类加载过程
class ----> loading (加载)----> linking(校验、准备、解析) ----> initializing(初始化)----> GC
Linking 中被分为了 verification、preparation、resolution 三个部分
loading:将字节码文件加载到内存中去
link:
verification:校验,验证class 字节码文件是否符合class文件的标准
preparation:准备,将class 中的静态变量赋默认值
resolution:消除,将class 中用到的符号引用转换为内存引用,可以理解为内存地址
initializing:这个时候将静态变量赋初始值,调用静态代码块
2、类加载器
类加载器就是执行 load 这一步,将字节码加载到内存中去
当将字节码加载到内存中去之后,也会创建一个Class类对象,指向字节码在内存中的位置,
通过这个对象对字节码进行解析
Boostrap <------ Extension <------ App <------ Custom ClassLoader
Boostrap:加载lib/rt.jar charset.jar 等核心类,C++ 实现
Extension:加载扩展jar包 jre/lib.ext/*.jar 或由 -Djava.ext.dirs 指定
App:加载classpath指定内容
CustomClassLoader:自定义ClassLoader
双亲委派机制:先自底向上检查该类是否已经加载,如果都没有加载,就自顶向下进行实际查找和加载child方向
为什么?
1、保证安全问题
2、保证不重复加载
源码分析:此次我们主要对ClassLoader 中的 loadClass方法和构造方法进行分析
2.1 构造方法源码分析
首先我们来看构造方法,构造方法能够解决我们很多疑问,例如ClassLoader 中的 parent 属性是什么时候赋值的。
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
当我们调用无参构造时,会调用ClassLoader 中的另外一个构造方法,我们来看传入的这两个参数的方法
checkCreateClassLoader()
private static Void checkCreateClassLoader() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
return null;
}
这个方法主要是鉴定权限, java.lang.SecurityManager.checkCreateClassLoader() 方法抛出一个SecurityException如果调用线程不允许创建新的类加载器。此方法调用checkPermission与RuntimePermission(“createClassLoader”)权限。如果重写此方法,那么调用super.checkCreateClassLoader重载方法通常会抛出一个异常。
然后就是 getSystemClassLoader()
方法
public static ClassLoader getSystemClassLoader() {
// 初始化系统类加载器
initSystemClassLoader();
if (scl == null) {
return null;
}
// 鉴权
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
在ClassLoader
内部维护了两个与加载系统类加载器相关的静态变量,从注释中我们可以看出scl
就是用来接收系统类加载器的,而sclSet
则是一个布尔值,用来判断scl
是否为空的,关于系统类加载器基本都是操作这两个值的。
// The class loader for the system
// @GuardedBy("ClassLoader.class")
private static ClassLoader scl;
// Set to true once the system class loader has been set
// @GuardedBy("ClassLoader.class")
private static boolean sclSet;
调用initSystemClassLoader()
,并且做了一些安全检查,直接返回系统类加载器scl
。
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
// 得到 Launcher
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
// 得到系统类加载器
scl = l.getClassLoader();
try {
// 是否有自定义类加载器
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
initSystemClassLoader
主要就是初始化系统类加载器的,但是若用户自定义了类加载器,就会将scl
初始化为用户自定义类加载器,这部分是通过ClassLoader
的一个内部类来实现的。
SystemClassLoaderAction
是一个内部类,最后就是通过这个内部类来完成最后的类加载器初始化工作的。
class SystemClassLoaderAction
implements PrivilegedExceptionAction<ClassLoader> {
private ClassLoader parent;
SystemClassLoaderAction(ClassLoader parent) {
this.parent = parent;
}
public ClassLoader run() throws Exception {
//从系统属性中获取key,即用户自定义类加载器的二进制名
String cls = System.getProperty("java.system.class.loader");
//若cls为空,则返回parent,即传入的系统类加载器
if (cls == null) {
return parent;
}
//通过这个自定义类加载器的二进制名,使用系统类加载器去将其加载,并将其初始化
Constructor<?> ctor = Class.forName(cls, true, parent)
.getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
//通过反射创建这个自定义类加载器的实例
ClassLoader sys = (ClassLoader) ctor.newInstance(
new Object[] { parent });
//将线程上下文类加载器设置为这个自定义类加载器
Thread.currentThread().setContextClassLoader(sys);
return sys;
}
}
2.2 loadClass 方法源码分析(双亲委派机制的实现)
然后我们来看 loadClass方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
当我们传入一个 name 时,其底层是调用了另一个 lodaClass 重载的方法,这里的第二个参数为 false 是表示 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 {
// 如果 parent 不为空,就先执行 父加载器中的loadClass 方法,就又回到了第一步
// 这里就能看到双亲委派机制了
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// parent 为空,表示为 bootstrap 类加载器,调用这个方法
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;
}
}
此方法的重要代码都有注释,通俗易懂。
2.3 关于 Launcher
3、自定义类加载器
自定义类加载器
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File file = new File("/Users/lcx/Desktop/ClassLoader/", name.replace(".", "/").concat(".class"));
try {
DataInputStream is = new DataInputStream(new FileInputStream(file));
int len = (int)file.length();
byte[] buff = new byte[len];
is.readFully(buff);
is.close();
return defineClass(name, buff, 0, buff.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader();
System.out.println(classLoader.getParent());
Class<?> helloWorld = classLoader.loadClass("HelloWorld");
helloWorld.getMethod("doSome",String.class).invoke(helloWorld.newInstance(),"lcx");
}
}
在传入的 /Users/lcx/Desktop/ClassLoader/
文件夹中有 HelloWorld.class , 这个字节码是使用我们自定义的类加载器来实现加载的。
public class HelloWorld{
public void doSome(String name){
System.out.println("hello " + name);
}
}
最终我们利用反射,执行这个类中的方法,实现了自定义类加载器。