类的生命周期
- 加载(Loading):找 Class 文件
- 验证(Verification):验证格式、依赖
- 准备(Preparation):静态字段、方法表
- 解析(Resolution):符号解析为引用
- 初始化(Initialization):构造器、静态变量赋值、静态代码块
- 使用(Using)
- 卸载(Unloading)

类的加载时机
- 当虚拟机启动时,初始化用户指定的主类,就是启动执行的 main 方法所在的类;
- 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类,就是 new 一个类的时候要初始化;
- 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
- 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
- 子类的初始化会触发父类的初始化;
- 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
- 使用反射 API 对某个类进行反射调用时,初始化这个类,其实跟前面一样,反射调用要
- 么是已经有实例了,要么是静态方法,都需要初始化;
- 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。
不会初始化(可能会加载)
- 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
- 定义对象数组,不会触发该类的初始化。
- 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
- 通过类名获取 Class 对象,不会触发类的初始化,Hello.class 不会让 Hello 类初始化。
- 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。(Class.forName”jvm.Hello”)默认会加载 Hello 类。
- 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是不初始化)。
三类加载器
- 启动类加载器(BootstrapClassLoader)
- 扩展类加载器(ExtClassLoader)
- 应用类加载器(AppClassLoader)

加载器特点
- 双亲委托
- 负责依赖
- 缓存加载

当前 ClassLoader 加载了哪些 Jar
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 
 | package jvm;
 import java.lang.reflect.Field;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.ArrayList;
 
 public class JvmClassLoaderPrintPath {
 
 public static void main(String[] args) {
 
 
 URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
 System.out.println("启动类加载器");
 for (URL url : urls) {
 System.out.println(" ===> " + url.toExternalForm());
 }
 
 printClassloader("扩展类加载器", JvmClassLoaderPrintPath.class.getClassLoader().getParent());
 printClassloader("应用类加载器", JvmClassLoaderPrintPath.class.getClassLoader());
 
 }
 
 private static void printClassloader(String name, ClassLoader classLoader) {
 System.out.println();
 if (null != classLoader) {
 System.out.println(name + " Classloader -> " + classLoader);
 printURLForClassloader(classLoader);
 } else {
 System.out.println(name + " Classloader -> null");
 }
 }
 
 private static void printURLForClassloader(ClassLoader classLoader) {
 Object ucp = insightField(classLoader, "ucp");
 Object path = insightField(ucp, "path");
 ArrayList paths = (ArrayList) path;
 assert paths != null;
 for (Object p : paths) {
 System.out.println(" ===> " + p.toString());
 }
 }
 
 private static Object insightField(Object obj, String fName) {
 Field f;
 try {
 if (obj instanceof URLClassLoader) {
 f = URLClassLoader.class.getDeclaredField(fName);
 } else {
 f = obj.getClass().getDeclaredField(fName);
 }
 f.setAccessible(true);
 return f.get(obj);
 } catch (Exception e) {
 e.printStackTrace();
 }
 return null;
 }
 }
 
 | 
自定义 ClassLoader
| 12
 3
 4
 5
 6
 7
 
 | package jvm;
 public class Hello {
 static {
 System.out.println("Hello Class Initialized!");
 }
 }
 
 | 
编译成字节码class文件,然后获取class文件的base64编码。

将base64编码放入自定义类加载器中初始化。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | package jvm;
 import java.util.Base64;
 
 public class HelloClassLoader extends ClassLoader {
 
 public static void main(String[] args) throws Exception {
 
 new HelloClassLoader().findClass("jvm.Hello").newInstance();
 }
 
 @Override
 protected Class<?> findClass(String name) {
 String helloBase64 = "yv66vgAAADQAHwoABgARCQASABMIABQKABUAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQALTGp2bS9IZWxsbzsBAAg8Y2xpbml0PgEAClNvdXJjZUZpbGUBAApIZWxsby5qYXZhDAAHAAgHABkMABoAGwEAGEhlbGxvIENsYXNzIEluaXRpYWxpemVkIQcAHAwAHQAeAQAJanZtL0hlbGxvAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAUABgAAAAAAAgABAAcACAABAAkAAAAvAAEAAQAAAAUqtwABsQAAAAIACgAAAAYAAQAAAAMACwAAAAwAAQAAAAUADAANAAAACAAOAAgAAQAJAAAAJQACAAAAAAAJsgACEgO2AASxAAAAAQAKAAAACgACAAAABQAIAAYAAQAPAAAAAgAQ";
 byte[] bytes = decode(helloBase64);
 return defineClass(name,bytes,0,bytes.length);
 }
 
 public byte[] decode(String base64) {
 return Base64.getDecoder().decode(base64);
 }
 }
 
 | 
添加引用类的几种方式
- 放到 JDK 的 lib/ext 下,或者 -Djava.ext.dirs
- java-cp/classpath 或者 class 文件放到当前路径
- 自定义 ClassLoader 加载
- 拿到当前执行类的 ClassLoader,反射调用 addUrl 方法添加 Jar 或路径(JDK9 无效)
在JDK8下,当前执行类的 ClassLoader,反射调用 addUrl 方法添加 Jar 或路径示例:
把之前Hello.java编译后的字节码文件放入到目录:c:/Users/maojun/lib/jvm/Hello.class。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | public class JvmAppClassLoaderAddURL {public static void main(String[] args) {
 
 String appPath = "file:/c:/Users/maojun/lib/";
 URLClassLoader urlClassLoader = (URLClassLoader) JvmAppClassLoaderAddURL.class.getClassLoader();
 try {
 Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
 addURL.setAccessible(true);
 URL url = new URL(appPath);
 addURL.invoke(urlClassLoader, url);
 
 Class.forName("jvm.Hello");
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }
 
 | 
在 JDK 9 及以上版本中,由于模块系统的引入,类加载器的实现发生了改变。AppClassLoader的实现不再是URLClassLoader的直接子类,而是由jdk.internal.loader.ClassLoaders$AppClassLoader实现
在JDK9之后的版本修改为下面的代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public class JvmAppClassLoaderAddURL {public static void main(String[] args) {
 
 String appPath = "file:/c:/Users/maojun/lib/";
 ClassLoader classLoader = JvmAppClassLoaderAddURL.class.getClassLoader();
 try {
 URL[] url = new URL[]{new URL(appPath)};
 Class.forName("jvm.Hello",true,new URLClassLoader(url));
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }
 
 | 
(完)