一、类的加载机制
在我们初学Java时,就知道,运行一个类,是通过如下几步
- 编译
- 运行
编译采用 javac命令,将Java源文件,编译成字节码文件(.class)
运行采用 java 命令,将对应的class字节码,加载入虚拟机运行
如下的测试类,即为 编译 javac TestLoader.java
运行: java TestLoader
public class TestLoader {
public static int a = 5;
public static void main(String []args) {
System.out.println("TestLoader");
}
}
在执行 java TestLoader
这行命令时,主要执行了如下的流程
- 去JRE的目录下,寻找 java.exe,java.exe 寻找 jvm.dll(c/c++的链接库),创建出java虚拟机进程
- java虚拟机,创建出 BootstrapClassLoader(引导类加载器)
- C++代码,调用 Java代码,创建 Launcher 类,Launcher类负责加载其他的 classLoader
- 各ClassLoader去对应的目录下,加载Class文件,通过反射,执行main方法,结束
其中,加载Class文件又细分了许多的步骤
- 加载。 通过 磁盘的IO,将Class文件的内容,读取到内存中。在java中,只有使用到了某个类(例如new等操作),才会去加载某个类,加载完成之后,封装成
java.lang.Class
对象。 - 验证。 检验Class文件的合法性,Java对于 Class文件的结构有详细的规范,详见官方文档
- 准备。 赋予静态变量初始值,例如上述的例子中,将static变量a赋值为默认值0
- 解析。 将 符号引用 转换成 直接引用(例如将main这个符号,转化为数据所在的指针),即静态链接的过程(在类加载阶段就完成)。(动态链接在运行期间才生效)
- 初始化。 赋予静态变量真实值,并执行static代码块,例如上述的例子中,将static变量a赋值为用户设置的值 5
类加载之后,放入到JVM中的方法区中,在方法区中保存着
- 运行时常量池,
- 类型信息。 这个类,对应的类型是什么,如上为 TestLoader类型
- 方法信息。 这个类,类中有多少方法,如上只有main方法
- 类加载器引用。 这个类,被哪个类加载器所加载的。 是AppClassLoader,还是ExtClassLoader?
- 对应的class实例引用。 对应的Class对象
类的懒加载(使用到某个类之后,才会加载某个类)
当并没有执行 new B() 时,并不会打印出 B 类中的静态代码块,说明,此时B类并没有被加载。
public class LazyLoad {
public static void main(String[] args) {
new A();
B b = null;
// B b = new B();
}
}
class A {
static {
System.out.println("class A loading");
}
public A() {
System.out.println("init A");
}
}
class B {
static {
System.out.println("class B loading");
}
public B() {
System.out.println("init B");
}
}
输出结果如下
class A loading
init A
二、类加载器
Java中的类加载器主要有如下几种
- BootstrapClassLoader (启动类加载器),加载JVM的核心库,例如rt.jar
- ExtClassLoader (扩展类加载器), 加载扩展Jar包,例如 jre/lib/ext 下的jar包
- AppClassLoader (应用程序类加载器),负责加载当前classpath路径下的类,自己写的类,主要靠AppClassLoader来加载
- CustomClassLoader (自定义ClassLoader),根据自定义的规则,加载指定路径下的类
类加载器验证与使用
1. 验证3种加载器
```java System.out.println(String.class.getClassLoader()); System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); System.out.println(TestClassLoader.class.getClassLoader().getClass().getName());
System.out.println(); ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); ClassLoader extClassloader = appClassLoader.getParent(); ClassLoader bootstrapLoader = extClassloader.getParent(); System.out.println(“the bootstrapLoader : “ + bootstrapLoader); System.out.println(“the extClassloader : “ + extClassloader); System.out.println(“the appClassLoader : “ + appClassLoader);
打印结果
```java
null // BootstrapClassLoader,启动类加载器,由于C++加载,所以打印不出来
sun.misc.Launcher$ExtClassLoader // 扩展类加载器,主要加载 ext
sun.misc.Launcher$AppClassLoader // 应用程序加载器,加载自定义的类,例如 TestClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@12a3a380
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
2. 三种加载器对应的加载路径
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
打印结果
bootstrapLoader加载以下文件:
file:/home/ifan/software/java/jdk1.8/jre/lib/resources.jar
file:/home/ifan/software/java/jdk1.8/jre/lib/rt.jar
file:/home/ifan/software/java/jdk1.8/jre/lib/sunrsasign.jar
file:/home/ifan/software/java/jdk1.8/jre/lib/jsse.jar
file:/home/ifan/software/java/jdk1.8/jre/lib/jce.jar
file:/home/ifan/software/java/jdk1.8/jre/lib/charsets.jar
file:/home/ifan/software/java/jdk1.8/jre/lib/jfr.jar
file:/home/ifan/software/java/jdk1.8/jre/classes
extClassloader加载以下文件:
/home/ifan/software/java/jdk1.8/jre/lib/ext:/usr/java/packages/lib/ext
appClassLoader加载以下文件:
/home/ifan/software/java/jdk1.8/jre/lib/charsets.jar:/home/ifan/software/java/jdk1.8/jre/lib/deploy.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/cldrdata.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/dnsns.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/jaccess.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/jfxrt.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/localedata.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/nashorn.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/sunec.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/sunjce_provider.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/sunpkcs11.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/zipfs.jar:/home/ifan/software/java/jdk1.8/jre/lib/javaws.jar:/home/ifan/software/java/jdk1.8/jre/lib/jce.jar:/home/ifan/software/java/jdk1.8/jre/lib/jfr.jar:/home/ifan/software/java/jdk1.8/jre/lib/jfxswt.jar:/home/ifan/software/java/jdk1.8/jre/lib/jsse.jar:/home/ifan/software/java/jdk1.8/jre/lib/management-agent.jar:/home/ifan/software/java/jdk1.8/jre/lib/plugin.jar:/home/ifan/software/java/jdk1.8/jre/lib/resources.jar:/home/ifan/software/java/jdk1.8/jre/lib/rt.jar:/home/ifan/workspace/idea-workspace/juc/out/production/juc:/home/ifan/software/java/idea/lib/idea_rt.jar
类加载器的加载过程
在本文刚开始时,即说明了,C++将会调用Java,创建Launcher类,Launcher类负责加载其他的 classLoader
public Launcher() {
Launcher.ExtClassLoader var1;
try {
// 创建ExtClassLoader,没有传递任何参数,即它的父加载器为null,即为 BootstrapClassLoader
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 创建AppClassLoader,将var1(ExtClassLoader),设置为AppClassLoader的父加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// 设置默认的加载器,是AppClassLoader
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);
}
}
从上面的创建ExtClassLoader,以及创建AppClassLoader中,创建的时候传入了对应的父加载器(null/ExtClassLoader),就可以看出,这三个加载器的关系如下
再看ClassLoader#loadClass,加载类时
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
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();
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;
}
}
从上面这段代码就可以看出,loadClass的主要流程如下
- getClassLoadingLock ,利用ConcurrentHashMap进行加锁
- findLoadedClass 寻找已经加载过的类 (底层调用native方法,在C++层次维护),如果已经加载过了,直接返回,如果没有加载过,走3
- 尝试进行加载
- 如果有parent父加载器,那么,调用parent.loadClass,让父加载器去加载
- 如果没有父加载器,说明到了最顶层,调用native方法,去加载类
如果父加载器,都无法加载到这个类,执行
findClass(name)
,自己来找,将会去此类加载器对应的路径下,寻找类,如果到了最底层(AppClassLoader),还找不到这个类的话,那么将会抛出ClassNotFountException。从以上的分析中可以得出,Java的类加载是依照双亲委派机制来进行加载的,先有父加载器进行加载,父加载器中加载不到的话,再有自己来进行加载。
那为什么Java要使用双亲委派机制的方式来进行加载类呢?
主要原因有两点;
沙箱安全机制: 避免用户自定义包名类名相同的类,来篡改Java的核心类库
- 避免类的重复加载: 保证在内存中,只有同一个类信息,不需要每个加载器都去加载一遍