静态常量 静态常量如果在编译期能够被确定,则不会触发类初始化,甚至都不会进入类加载阶段,比如字符串常量。它们会直接放入常量池,并且在编译器优化中,直接将该常量放入到引用类的常量池中,也就是说生成的引用类 Class 文件中并不包含被引用类的符号引用,它们在 Class 文件中毫无关系了。 而编译期无法确定,必须在运行时才能确定的字段,这类静态常量会导致类初始化(只有在类初始化阶段才开始执行代码),比如当前时间 static final String STATIC_TIME = System.currentTimeMillis() + "";。
// 4. 设置 ContextClassLoader 类加载器 Thread.currentThread().setContextClassLoader(this.loader); ... } /* * Returns the class loader used to launch the main application. */ public ClassLoader getClassLoader(){ return loader; }
publicabstractclassClassLoader{ ... public Class<?> loadClass(String var1) throws ClassNotFoundException { returnthis.loadClass(var1, false); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 1. 先确认类是否已经被加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { // 2. 如果没有被加载,父加载器是否为空 // 不为空则父加载器加载,依次递归 c = parent.loadClass(name, false); } else { // 3. 父加载器为空,则启动类加载器加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader // 4. 如果父加载器抛出 ClassNotFoundException // 说明父加载器无法完成类加载请求 }
if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); // 5. 父加载器无法完成加载或者其他原因没有加载成功 // 则调用本身的 findClass 进行类加载 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; } } protected Class<?> findClass(String name) throws ClassNotFoundException { thrownew ClassNotFoundException(name); } ... publicfinal ClassLoader getParent(){ if (this.parent == null) { returnnull; } else { ... returnthis.parent; } } protectedfinal Class<?> defineClass(...) throws ClassFormatError { ... } publicstatic ClassLoader getSystemClassLoader(){ initSystemClassLoader(); if (scl == null) { returnnull; } ... return scl; }
privatestaticsynchronizedvoidinitSystemClassLoader(){ if (!sclSet) { if (scl != null) thrownew IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; scl = l.getClassLoader(); ... } sclSet = true; } } }
loadClass loadClass 传入的参数是类全名,比如:com.***.Test, com.***.Test$innerclass,并返回一个 Class 类型的实例。它实现了双亲委派模型,如果父加载器都没有加载成功,则调用 findClass 来查找。
publicstaticvoidmain(String[] args)throws InterruptedException { // do not loading class System.out.println("STATIC FINAL STR: " + Test.STATIC_STR); System.out.println("***********************************"); // loading & init class: Test System.out.println("STATIC FINAL TIME: " + Test.STATIC_TIME); Thread.sleep(1000); // STATIC_TIME value assigned when class loading. System.out.println("STATIC FINAL TIME: " + Test.STATIC_TIME); } }
[Opened /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] [Loaded java.lang.Object from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] ... // 1. 加载测试类 TestStaticField [Loaded TestStaticField from file:/home/xmt/test/classloading/] ... // 2. 直接输出静态常量结果,没有执行 Test 类初始化,甚至类加载阶段都没有 STATIC FINAL STR: TestStaticFieldStr *********************************** // 3. 加载类 Test [Loaded TestStaticField$Test from file:/home/xmt/test/classloading/] // 4. Test 类初始化,并在运行时对 STATIC_TIME 赋值 Test Class Init. STATIC FINAL TIME: 1525837181577 // 5. STATIC_TIME 被 final 修饰,只会被赋值一次 STATIC FINAL TIME: 1525837181577 [Loaded java.lang.Shutdown from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] [Loaded java.lang.Shutdown$Lock from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar]
静态字段 STATIC_STR 编译时确定,属于常量,不会触发类初始化,甚至不会进入类加载阶段。
静态字段 STATIC_TIME 运行时确定,会触发类初始化并运行代码计算时间。STATIC_TIME 被 final 修饰,只会被赋值一次。
在静态代码块中,只能对定义在之后的静态变量赋值,不能访问!否则会提示 Illegal forward reference. 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
[Opened /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] [Loaded java.lang.Object from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] ... // 1. 加载测试类 TestClassInitSeq [Loaded TestClassInitSeq from file:/home/xmt/test/classloading/] [Loaded java.lang.Void from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] // 2. 先加载父类,再加载子类 [Loaded TestClassInitSeq$Father from file:/home/xmt/test/classloading/] [Loaded TestClassInitSeq$Son from file:/home/xmt/test/classloading/] ... // 3. 先初始化父类,再初始化子类 Father Class Init. Son Class Init. Son.number = 3 [Loaded java.lang.Shutdown from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] [Loaded java.lang.Shutdown$Lock from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar]
[Opened /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] [Loaded java.lang.Object from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] ... // 1. 系统加载器加载: TestClassInstanceOf 和自定义类加载器 [Loaded TestClassInstanceOf from file:/home/xmt/test/classloading/] [Loaded TestClassInstanceOf$1 from file:/home/xmt/test/classloading/] ... // 2. 自定义类加载器开始加载:TestClassInstanceOf fileName = TestClassInstanceOf.class [Loaded java.lang.Throwable$PrintStreamOrWriter from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] ... java.lang.Exception: This is a log. at TestClassInstanceOf$1.loadClass(TestClassInstanceOf.java:12) at TestClassInstanceOf.main(TestClassInstanceOf.java:30) // 3. defineClass 中会递归加载 TestClassInstanceOf 的父类 Object // 自定义加载器中,父类由双亲委派模型处理 fileName = Object.class java.lang.Exception: This is a log. at TestClassInstanceOf$1.loadClass(TestClassInstanceOf.java:12) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:800) at java.lang.ClassLoader.defineClass(ClassLoader.java:643) at TestClassInstanceOf$1.loadClass(TestClassInstanceOf.java:21) at TestClassInstanceOf.main(TestClassInstanceOf.java:30) cant find class file: java.lang.Object // 4. 自定义类加载器:从虚拟机中成功加载 TestClassInstanceOf // 将字节文件转换为方法区的运行时数据结构 [Loaded TestClassInstanceOf from __JVM_DefineClass__] clazz = class TestClassInstanceOf // 5. 对类 TestClassInstanceOf 进行反射调用时,触发类加载过程的类初始化阶段 // 初始化阶段:如果类没有初始化,需要先触发类初始化;如果父类没有初始化,先触发父类初始化 // 这里触发的是自定义类加载器自身及其父类 ClassLoad 的类初始化 // 父类 ClassLoad 类加载由双亲委派模型去处理 fileName = ClassLoader.class java.lang.Exception: This is a log. at TestClassInstanceOf$1.loadClass(TestClassInstanceOf.java:12) at java.lang.Class.getDeclaredConstructors0(Native Method) at java.lang.Class.privateGetDeclaredConstructors(Class.java:2595) at java.lang.Class.getConstructor0(Class.java:2895) at java.lang.Class.newInstance(Class.java:354) at TestClassInstanceOf.main(TestClassInstanceOf.java:32) cant find class file: java.lang.ClassLoader // 6. 自定义类加载器开始加载自己: TestClassInstanceOf$1 fileName = TestClassInstanceOf$1.class java.lang.Exception: This is a log. at TestClassInstanceOf$1.loadClass(TestClassInstanceOf.java:12) at java.lang.Class.getDeclaredConstructors0(Native Method) at java.lang.Class.privateGetDeclaredConstructors(Class.java:2595) at java.lang.Class.getConstructor0(Class.java:2895) at java.lang.Class.newInstance(Class.java:354) at TestClassInstanceOf.main(TestClassInstanceOf.java:32) defineClass. // 7. 自定义类加载器:从虚拟机中成功加载自己 TestClassInstanceOf$1 [Loaded TestClassInstanceOf$1 from __JVM_DefineClass__] clazz = class TestClassInstanceOf$1 // 8. 使用 instanceof 比较,同一个类不同类加载器加载后,并不相等 object = class TestClassInstanceOf the same class but two different classLoaders, instanceof = false [Loaded java.lang.Shutdown from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar] [Loaded java.lang.Shutdown$Lock from /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/rt.jar]
重写 loadClass 方法的注意事项:
如果找不到指定 class 文件,调用父类方法处理 当找不到指定 class 文件时,需要父类方法的双亲委派模型处理,比如 defineClass 会加载父类,而很多基础父类组件如 Object 是三大父加载器加载的。
被加载类 class 文件位置 因为重写 loadClass 可以直接避开双亲委派模型,所以任意位置的 class 文件都可以正确解析。
疑问:自定义类加载器为什么要加载自己 TestClassInstanceOf$1 ? 如果将自定义类加载器改为 static class 静态内部类,则不会加载自己。反编译 TestClassInstanceOf$1.class 文件,可以发现 static 方法内的匿名内部类,反编译后是一个 final class,而且和静态内部类一样,不会引用外部类 this 。
privatebyte[] loadClassData(String s) throws IOException { // 判断给定的 class 文件目录,及对应的 class 文件是否存在 File dir = new File(mClassFileDir); if (!dir.exists()){ thrownew IOException("mClassFileDir is not exists."); }
String fileName = s + ".class"; File classFile = new File(dir.getAbsolutePath(), fileName);
if (!classFile.exists()){ thrownew IOException(fileName + " file is not exists."); }
// 如果 class 文件存在,读取到数组中 byte[] data = null; FileInputStream fileInputStream = new FileInputStream(classFile); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int count = 0; while ((count = fileInputStream.read()) != -1) { byteArrayOutputStream.write(count); } data = byteArrayOutputStream.toByteArray();
// 输出结果 MyClassLoader::findClass loadClassData: classFilePath = C:\Users\xmt\***\src\main\resources\Test.class MyClassLoader::findClass, clazz = classTest Test.class file is loaded by: com.***.classloading.MyClassLoader@6bc7c054 It is Test::test.
重写 findClass 方法的注意事项:
被加载类 class 文件位置 被加载类 Test.class 文件,不能放在 MyClassLoader 文件所在目录及其子目录中。重写 findClass 保留了双亲委派模型,会被 AppClassLoader 系统加载器扫描到并加载,自定义加载器无法生效。 在 Android Studio 中,这个 class 文件甚至不能直接由 AS 生成。打印 AppClassLoader 加载路径为:...;C:\Users\xmt\AndroidStudioProjects\xmt\gitlab\04_androidbasic\android_basic_knowledge\javabase\build\classes\java\main;...,也就是说,所有由 AS 工具生成的 class 文件都会默认被系统加载器加载。
自定义加载器 判断传入的 class 文件目录及对应文件是否存在,如果存在则先转换为数组,并通过 defineClass 将二进制文件转换为运行时数据结构。