JVM加载Class文件主要分3个过程:Loading 、Linking、Initialzing
1. Loading
这个过程主要是将class文件加载到内存中,主要理解双亲委派模型和ClassLoader
-
类加载器
JDK提供的三个ClassLoader为Launcher的内部类。相关截图都在sun.misc.Launcher中
BootClassLoader 加载范围sun.boot.class.path
ExtClassLoader 加载范围java.ext.dirs
AppClassLoader 加载范围java.class.path
CustomClassLoader 可自定义加载范围
-
双亲委派模型
graph BT A(.class)-->B(CustomClassLoader)--加载过-->Z((返回结果)) B(CustomClassLoader)--未加载过-->C(AppClassLoader)--加载过-->Z((返回结果)) C(AppClassLoader)--未加载过-->D(ExtClassLoader)--加载过-->Z((返回结果)) D(ExtClassLoader)--未加载过-->E(BootClassLoader)--加载过-->Z((返回结果)) E(BootClassLoader)==未加载过,自己可以加载===>Z((返回结果)) E(BootClassLoader)==自己不可以加载==>D(ExtClassLoader) D(ExtClassLoader)==自己可以加载===>Z((返回结果)) D(ExtClassLoader)==自己不可以加载==>C(AppClassLoader) C(AppClassLoader)==自己可以加载===>Z((返回结果)) C(AppClassLoader)==自己不可以加载===>B(CustomClassLoader) B(CustomClassLoader)==自己可以加载===>Z((返回结果)) B(CustomClassLoader)==自己不可以加载===>异常((ClassNotFoundException))1、防止重复加载同一个.class,通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据 安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加 载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对 象。这样保证了Class执行安全。 -
双亲委派源码体现
/**
* 从调用方式上看class文件的加载类似递归加载。
* ClassLoader采用的是模板方法的设计模式,只需要重写findClass()方法就能实现自己的ClassLoader
*/
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
//检查class文件是否加载过,如果加载过就返回,如果没有加载继续加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//parent中加载,由继续执行loadClass方法,有就返回
c = parent.loadClass(name, false);
} else {
//如果没有parent就由BootClassLoader加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
//如果当前ClassLoader没有找到,就去加载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;
}
}
- 自定义ClassLoader
public class CustomClassLoader extends ClassLoader{
//
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
try {
File file = new File(name);
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = fis.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
Class<?> clazz = defineClass(null, bytes, 0, bytes.length);
System.out.println(clazz);
return clazz;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Test
public void classLoaderTest() throws Exception{
CustomClassLoader customClassLoader = new CustomClassLoader();
Class<?> clazz = customClassLoader.loadClass("D:\\zyouke_git\\zyouke\\java_base\\out\\production\\java_base\\spi\\IHelloSpi.class");
System.out.println(clazz);
}
}
- 如何破坏双亲委派模型
- 自定义ClassLoader重写loadClass()
- 自定义ClassLoader,在构造方法中调用super(parent)指定parent
2. Linking
Linking链接的过程分3个阶段:Vertification、Preparation、Resolution。
Vertification: 验证Class文件是否符合JVM规定。
Preparation:给静态成员变量赋默认值。
Resolution:将类、方法、属性等符号引用解释为直接引用;常量池中的各种符号引用解释为指针、偏移量等内存地址的直接引用
3. Initialzing
调用初始化代码clint,给静态成员变量赋初始值。 这里可以了解下必须初始化的5种情况:
1,new getstatic putstatic invokestatic指令,访问final变量除外。
2,java.lang.reflect对类进行反射调用时。
3,初始化子类的时候,父类必须初始化。
4,虚拟机启动时,被执行的主类必须初始化。
5,动态语言支持java.lang.invoke.MethodHandler解释的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化。
扩展之tomcat是如何打破双亲委派模型,为什么要打破双亲委派模型
-
URLClassLoader
commonLoader,catalinaLoader,sharedLoader都是基于URLClassLoader创建的。public static void main(String[] args) throws Exception{ // 创建一个url数组,数组里面的元素是从maven库中找的jar URL[] urlArr = new URL[]{new URL("file:C:/Users/Administrator/.m2/repository/org/objenesis/objenesis/2.2/objenesis-2.2.jar")}; URLClassLoader urlClassLoader = new URLClassLoader(urlArr,Thread.currentThread().getContextClassLoader()); // org.objenesis.ObjenesisBase是这个jar下的class Class<?> clazz = urlClassLoader.loadClass("org.objenesis.ObjenesisBase"); System.out.println(clazz.getClassLoader()); System.out.println(clazz); } //打印结果 //加载类的classLoader java.net.URLClassLoader@e2d56bf //class org.objenesis.ObjenesisBase -
tomcat自定义commonLoader,catalinaLoader,sharedLoader
//org.apache.catalina.startup.Bootstrap#initClassLoaders //在tomcat启动时会调用 private void initClassLoaders() { try { /** * 创建commonLoader,会使用catalina.properties配置文件中 * common.loader配置的jar构建URLClassLoader返回,parent传的null时,在后续的逻辑 * 中其实使用的是java.lang.ClassLoader#getSystemClassLoader()返回的classLoader * 作为parent */ commonLoader = createClassLoader("common", null); if( commonLoader == null ) { commonLoader=this.getClass().getClassLoader(); } /** * catalinaLoader,会使用catalina.properties配置文件中 * cserver.loader配置的jar构建URLClassLoader返回,parent使用的是commonLoader */ catalinaLoader = createClassLoader("server", commonLoader); /** * catalinaLoader,会使用catalina.properties配置文件中 * shared.loader配置的jar构建URLClassLoader返回,parent使用的是commonLoader */ sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); } } -
WebappClassLoader
一个 Tomcat 可能会部署多个这样的 web 应用,不同的 web 应用可能会依赖同一个第三方库的不同版本, 为了保证每个 web 应用的类库都是独立的,需要实现类隔离。而Tomcat 的自定义类加载器 WebAppClassLoader 解决了这个问题,每一个 web 应用都会对应一个 WebAppClassLoader 实例,不同的类加载器实例加载的类是不同的,Web应用之间通各自的类加载器相互隔离。
在其父类WebappClassLoaderBase重写了ClassLoaderfindClass和loadClass方法
//省略部分代码对关键部分解释 public Class<?> findClass(String name) throws ClassNotFoundException { Class<?> clazz = null; try { try { //先在 Web 应用目录下查找类 clazz = findClassInternal(name); } catch(AccessControlException ace) { throw new ClassNotFoundException(name, ace); } catch (RuntimeException e) { throw e; } if ((clazz == null) && hasExternalRepositories) { try { //在 Web 应用目录下查找不到类,交给父类加载 clazz = super.findClass(name); } catch(AccessControlException ace) { throw new ClassNotFoundException(name, ace); } catch (RuntimeException e) { throw e; } } //父类加载不到就抛异常 if (clazz == null) { throw new ClassNotFoundException(name); } } catch (ClassNotFoundException e) { throw e; } }public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // (0) 检查本地缓存是否有 clazz = findLoadedClass0(name); if (clazz != null) { return (clazz); } // (1) 从系统中去加载 clazz = findLoadedClass(name); if (clazz != null) { return (clazz); } String resourceName = binaryNameToPath(name, false); // 获取java的类加载器,并加载 ClassLoader javaseLoader = getJavaseClassLoader(); if (tryLoadingFromJavaseLoader) { try { clazz = javaseLoader.loadClass(name); if (clazz != null) { return (clazz); } } catch (ClassNotFoundException e) { } } try { // 尝试在本地目录搜索 class 并加载 clazz = findClass(name); if (clazz != null) { return (clazz); } } catch (ClassNotFoundException e) { } //都找不到抛出异常 throw new ClassNotFoundException(name); }