今天想利用类加载机制写一个单例类,结果发现自己不会写了,内部类没有写成static,搞不懂为什么在外部类中不能new这个内部类——当然不能,因为外部类的这个方法是static的,而这个内部类不是static,不要以为这样就结束了,原理你清楚吗?
参考文献
Internals of Java Class Loading
深入探讨Java类加载器
Class和Data
class代表一段可执行的代码(code),data代表这段代码的情形(state).
白话文就是class就像一个简历模板,data就是填写的信息.
state是会改变的,而code一般不会改变.class+data就是一个instance.
每个class都会在一个.class文件中保存它的信息,任何java类被编译时都会被嵌入一个类型是java.lang.Class的class字段,我们可以这样得到它:java.lang.Class clazz = Myclass.class;重点来了,一旦一个class被JVM加载了,相同的class就不会再被加载.哎哎哎,怎样才叫”相同的class”?在JVM中,一个完整的类名包括包名和类名本身,除此之外JVM还会结合加载这个class的ClassLoader实例来判断两个类是否相同.假如class叫Cl,in package Pg中,被KlassLoader实例Kll加载,那么Cl在JVM中的唯一标识就是(Cl, Pg, Kll),这就意味着(Cl, Pg, Kll)和(Cl, Pg, Cll)是两个不同的类.在JVM中它们不能cast,永远不能成为对方的实例.
那么,在JVM中,到底有多少ClassLoader的实例呢?
Class Loaders
在JVM中,所有class都是被java.lang.ClassLoader的一些实例加载的.我们也可以继承它来定制自己的classloader.
当敲入命令java MyMainClass启动JVM时,the “bootstrap class loader”负责加载Java核心类如java.lang.Object和其它运行时类(runtime code)到内存中.运行时类在JRE\lib\rt.jar文件中.我们是获取不到bootstarp class loader的任何细节的,因为它是native实现,同样,bootstrap class loader进入JVM的行为肯定有异于其它classloader.
例证一下,尝试获取一个核心类的classloader,你只会得到一个null.
1 |
|
接下来,就是java扩展类加载器(Java extension class loader).我们可以保存一些与core java runtime code无关的外部libraries,放在java.ext.dirs属性指定的目录下(/usr/lib/jvm/jdk8/jre/lib/ext
),ExtClassLoader会负责把所有jar文件全部加载进来.
第三种,也是最重要的class loader,APPClassLoader.它会把java.class.path属性下所有的classes都加载进来.
1 | -> echo $CLASSPATH |
其它一些class loader还有:
java.net.URLClassLoader
java.security.SecureClassLoader
java.rmi.server.RMIClassLoader
sum.applet.AppletClassLoader
java.lang.Thread包含一个方法public ClassLoader getContextClassLoader().它是由线程的创建者提供用来加载classes和resources的.如果当前线程没有设置,就会默认使用它父线程的class loader context.
How Class Loaders Work
除bootstrap class loader之外所有的class loader都有父类class loader.并且所有class loader的类型都是java.lang.ClassLoader.理解上面这两句话对开发自己的ClassLoader非常重要.最关键的就是正确设置父类ClassLoader.父类Classloader就是加载该Classloader的对象实例.(记住,一个classloader本身也是个class)
它是如何工作的呢,来看下源码:
1 | protected Class<?> loadClass(String name, boolean resolve) |
自己写ClassLoader时要记得设置parent
1 | public class MyClassLoader extends ClassLoader{ |
<<Java语言规范>>
给出了详细的loading,linking,initialization处理过程.
基本的就是以上这些,根据这个可以看看第二篇文章,为什么两个classloader加载的类可以cast,试试找出原因,让它们不能cast.如果你能够使这个示例报错,说明你已经掌握了classloader的原理了.
忽然看到开头的疑问,本来是想通过了解ClassLoader来解释内部类加载的问题.内部类的加载看来要分开分析了.
1 | package commons; |
终于可以把上面的坑填了,其实上面这种懒加载写法是关于Class加载时哪些值会被初始化的问题。
1 | public class Test { |
Class初始化顺序:
- static variables and static initializers in order
- instance variables and instance initializers in order
- constructors
参考https://www.geeksforgeeks.org/order-execution-initialization-blocks-constructors-java/
https://www.baeldung.com/java-initialization