一、Java类加载机制
1.1 类加载过程
Java虚拟机是由C++实现,java进程在启动时会创建一个引导类加载器的实例(C++实现),C++会调用Java代码创建Jvm启动器实例sun.misc.Launcher,该类由引导类加载器加载,该类负责创建其他类加载器。整个类加载的主要流程如下:
其中,loadClass的类加载过程的步骤如下:
- 加载:在硬盘上查找并通过IO读入.class文件,使用到该类时才会加载,例如new一个新对象,执行main方法的类等;
- 验证:校验字节码文件的正确性;
- 准备:给加载的类中的静态变量分配内存,并赋予默认值;
- 解析:将符号引用替换为直接引用,该阶段会将一些静态方法替换为指向数据所存的内存的指针或者句柄;
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块。
类加载后,类中含有的类型信息、字段信息、方法信息、类加载器的引用、对应class对象实例的引用、常量等信息会被加载到元空间中。
类的加载不是一次性将所有的类全部在主类启动的时候加载进虚拟机的运行时数据区,而是如果在执行的过程中使用到了其他类,会逐步加载这些类,同理jar包和war包中的类也不是一次性加载的,也是使用到的时候才加载
1.2 类加载器总结
类加载器是实现类加载的实例工具
1.2.1 类加载器
Java中的类加载器一共有四种:
- 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等;
- 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包;
- 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类;
- 自定义类加载器:负责加载用户自定义路径下的类包。
1.2.2 类加载器初始化过程源码剖析
类加载器初始化时会创建JVM启动容器实例sun.misc.Launcher
private static Launcher launcher = new Launcher();
public static Launcher getLauncher() {
return launcher;
}
首先,这个类是个典型的单例模式的类,这样保证在一个虚拟机内始终只有一个该类的实例。然后我们再来看下其构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
初始化的过程首先会创建一个ExtClassLoader对象(扩展类加载器),Launcher.ExtClassLoader.getExtClassLoader()这个方法点到里面也可以看到扩展类加载器使用的也是单例模式
private static volatile Launcher.ExtClassLoader instance;
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader();
}
}
}
return instance;
}
创建完扩展类加载器后,将其传入Launcher.AppClassLoader.getAppClassLoader(var1)
中创建应用类加载器
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
在这里就会去拿我们在安装jdk时配置的java.class.path的环境变量的值了,实际上就是jdk的安装路径,通过这个路径去获取相应的.class文件。
1.3 双亲委派机制
Java中的类加载器首先是有层级关系的,如下图
所谓的双亲委派机制,其实就是在加载某个类时,首先会委托其父类加载器加载,父类加载器又会委托其父类加载器加载,最顶层加载器(引导类加载器)若没加载成功,则由下层子加载器加载,以此类推。简单总结就是,先有父加载器加载,父加载器加载失败则由子加载器加载。