回顾
上一节我们学习了JVM类加载机制,我们学习到大概的过程:通过类加载器将编译好的class文件加载到JVM进程中,通过字节码执行引擎去执行代码。这只是一个整体的过程,具体的细节我们从本节开始分析。
通过本节我们将掌握以下知识:
- 类从加载到使用的整个过程
- 4种常用的类加载器
- 从源码角度分析类加载器初始化的全过程
- 双亲委派机制
- 自定义类加载器
- 打破双亲委派机制
本节内容干活满满,学完必有所收获!!!
1.类,从加载到使用
类从加载到使用一共分为6个阶段:加载、验证、准备、解析、初始化、使用;我们详细分析以下每个阶段具体做了什么事情。
加载
在用到某个类的时候,JVM会去class文件中将这个类加载到JVM中。如果是刚启动的程序,一般会以main()方法作为程序的主入口,把main()方法所在的类加载进来,然后从main() 方法开始执行代码,执行过程中用到哪个类,再去class文件中加载。
验证
根据Java虚拟机规范,校验加载进来的class文件是否符合指定的规范。所以把class文件加载到内存后,必须先验证以下,验证通过之后交给JVM虚拟机。
准备
给加载到JVM虚拟机的类分配内存空间,并且会给类变量(static修饰)分配内存空间,并给默认值。
解析
将符号引用转换为直接引用。
初始化
类变量赋初始值,并执行静态代码块中的代码。
2.类加载器
主要有4种类加载器:引导类加载器、扩展类加载器、应用程序类加载器、自定义类加载器。每种类加载器负责加载不同路径的类。
引导类加载器
Bootstrap ClassLoader,主要负责加载Java安装目录下,lib目录中的核心类库。
扩展类加载器
Extension ClassLoader,主要负责加载Java安装目录下,lib\ext目录下的类。
应用程序类加载器
Application ClassLoader,负责加载“ClassPath”环境变量所指定的路径中的类,就是我们自己写的类。
自定义类加载器
根据自己的需求,定义自己的类加载器。
3.类加载器初始化的全过程
我们知道有4种类加载器,除了自定义的类加载器以外,我们需要知道其他3种类加载器是怎样来的。
① 在创建JVM进程时,通过Java.exe调用底层的jvm.dll文件创建Java虚拟机,并创建引导类加载器。
② C++调用Java代码创建JVM启动器实例Launcher类的getLauncher()方法。
③ 通过Launcher类的无参构造方法创建一个Launcher对象,在这个无参构造方法内依次创建了扩展类加载器、应用程序类加载器,并将应用程序类加载器设置为默认的类加载器。
public class 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);}
}
}
public ClassLoader getClassLoader() {return this.loader;}
4.双亲委派机制
4.1 什么是双亲委派机制
上面说到的类加载器,是具有亲子结构的。当使用默认的类加载器-应用程序类加载器去加载某个类时,首先会委派给自己的父加载器-扩展类加载器去加载,扩展类加载器再委派给自己的父加载器-引导类加载器去加载。
如果引导类加载器在自己负责的目录下没有找到对应的类,就会下推到自己的子加载器-扩展类加载器,子加载器再去自己负责的目录下查找,如果没有再次下推.....
如果推到最下层依然没有找到,就会报错。
4.2 为什么需要双亲委派机制
双亲委派机制有两个优点:
① 沙箱安全机制
例如:自己写的java.lang.String不会被加载,防止核心API库被篡改。
② 避免重复加载
当父亲已经加载过某个类时,子加载器就没有必要去加载了,保证一个类全局只加载一次。
4.3 双亲委派机制的源代码解析
双亲委派机制的源码在ClassLoader类的loadClass()方法内实现的。我们研究一下源码。
① 先调用findLoadClass()方法,根据包名看一下这个类是否已经被加载。
② 如果没有被加载过,就会走双亲委派的代码,一直递归的调用loadClass()方法,委托父加载器去加载。
③ 当父加载器为空时,即到了最顶层的引导类加载器,将用引导类加载器去加载这个类。
④ 如果启动器类加载器没有加载到,就调用findClass()方法去加载(向下查找),底层调用的是URLClassLoader去加载的。
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// 首先,检查一下这个类是否已经被加载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();// 调用URLClassLoader的findClass()方法在加载器的类路径里查找并加载该类c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}
5.自定义类加载器
ClassLoader类主要包含两个主要方法:loadClass()、findClass()方法。
- loadClass():实现了双亲委派机制
- findClass():默认为空,自定义类加载器就是实现这个方法即可。
/*** 自定义类加载器*/
public class MyClassLoader extends ClassLoader{private String classPath;public MyClassLoader(String classPath){this.classPath = classPath;}/*** findClass方法,参数为全类名* 思路:* 1.根据全类名找到对应的类,将这个类转换为字节数组* 2.调用defineClass()方法* @param name* @return* @throws ClassNotFoundException*/@SneakyThrows@Overrideprotected Class<?> findClass(String name){byte[] bytes = loadClassToByte(name);return defineClass(name, bytes, 0, bytes.length);}/*** 根据类全路径,找到对应类,转换为字节数组** @return*/private byte[] loadClassToByte(String name) throws IOException {name = name.replaceAll("\\.","/");// 根据全路径找到类,转换为输入流FileInputStream fileInputStream = new FileInputStream(classPath+"/"+name+".class");int available = fileInputStream.available();byte[] bytes = new byte[available];fileInputStream.read(bytes);fileInputStream.close();return bytes;}
}
使用自定义的类加载器
public class JvmTest {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {MyClassLoader myClassLoader = new MyClassLoader("D:/tmp");Class<?> aClass = myClassLoader.loadClass("com.zzl.study.cloud.jvm.jvm.User");Object o = aClass.newInstance();Method method = aClass.getDeclaredMethod("usersay", null);Object invoke = method.invoke(o, null);System.out.println("类加载器"+aClass.getClassLoader().getClass().getName());}
}
6.打破双亲委派机制
打破双亲委派机制的核心在于:重写ClassLoader的loadClass()、findClass()方法。
/*** 打破双亲委派机制* 思路:* 1.重写loadClass()方法* 2.实现findClass()方法*/
public class MyClassLoader2 extends ClassLoader{private String classPath;public MyClassLoader2(String classPath){this.classPath = classPath;}@Overridepublic Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t1 = System.nanoTime();// 特定的一批类,直接走自定义的findClass去加载不需要走双亲委派if (!name.startsWith("com.zzl.study")){c = this.getParent().loadClass(name);}else {c = findClass(name);}// this is the defining class loader; record the statssun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}if (resolve) {resolveClass(c);}return c;}}@SneakyThrows@Overrideprotected Class<?> findClass(String name) {byte[] bytes = loadClassToByte(name);return defineClass(name, bytes, 0, bytes.length);}/*** 根据类全路径,找到对应类,转换为字节数组** @return*/private byte[] loadClassToByte(String name) throws IOException {name = name.replaceAll("\\.","/");// 根据全路径找到类,转换为输入流FileInputStream fileInputStream = new FileInputStream(classPath+"/"+name+".class");int available = fileInputStream.available();byte[] bytes = new byte[available];fileInputStream.read(bytes);fileInputStream.close();return bytes;}
}
public class JvmTest {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {//测试打破双亲委派机制MyClassLoader2 myClassLoader2 = new MyClassLoader2("D:/tmp");Class<?> aClass = myClassLoader2.loadClass("com.zzl.study.cloud.jvm.jvm.User");Object o = aClass.newInstance();Method method = aClass.getDeclaredMethod("usersay", null);Object invoke = method.invoke(o, null);MyClassLoader2 myClassLoader3 = new MyClassLoader2("D:/tmp1");Class<?> aClass3 = myClassLoader3.loadClass("com.zzl.study.cloud.jvm.jvm.User");Object o3 = aClass3.newInstance();Method method3 = aClass3.getDeclaredMethod("usersay", null);Object invoke3 = method3.invoke(o3, null);}
}