java的类加载机制详解

写在前面

通过本文的学习,你将了解到;

1.什么是类加载器?类加载的作用?

2.类加载的机制是什么样的?

什么是类加载器

类加载器,简称ClassLoader,ClassLoader的主要作用是将class文件加载到jvm中

但是在jvm启动的时候,并不会一次性加载全部的class文件,class文件的加载是一个动态加载的过程。

class文件

我们在编写代码的时候,比如在eclipse,intelij等ide中写的都是java文件,需要编译成class文件,才能执行


Test.java代码:

public class Test {
	
	     public static void main(String[] args) {
			System.out.println("abc");
		}

}

通过在cmd中执行:

javac Test.java

就会生成Test.class文件

在cmd中执行:

java Test

则可打印出执行结果abc.

class文件是java文件编译后生成的字节码文件,因为jvm并不能识别我们写的源代码java文件,所以需要使用javac编译成java虚拟机可以识别运行的class文件。(其他语言生成的.class文件,java虚拟机也是可以识别运行的)

java中默认的三个类加载器

BootStrap ClassLoader,ExtClassLoader,AppClassLoader

BootStrap ClassLoader是最顶层的类加载器,是底层的一个实现,嵌入在jvm的内核当中,用c/c++编写,在java中找不到对应的实例。负责加载jdk中核心的类库比如:rt.jar,resources.jar

可以通过从属性sun.boot.class.path中看到BootStrap ClassLoader主要是从哪些地方加载相关的jar或class文件

System.out.println(System.getProperty("sun.boot.class.path"));

如下所示

D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\resources.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\rt.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\sunrsasign.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\jsse.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\jce.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\charsets.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\jfr.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\classes

ExtClassLoader:Extension ClassLoader 扩展的类加载器,加载目录为:%JRE_HOME%\lib\ext目录下的class文件。

AppClassLoader:加载当前应用中的classpath下的所有类

我们可以测试下:

	     public static void main(String[] args) {
			System.out.println(Test.class.getClassLoader().toString());
			System.out.println(String.class.getClassLoader().toString());
		}

打印结果为:

sun.misc.Launcher$AppClassLoader@4e0e2f2a
Exception in thread "main" java.lang.NullPointerException
	at test.Test.main(Test.java:10)

说明我们自己编写都Test类,是由AppClassLoader加载器进行加载的

但是String类,报的是空指针异常,原因就是上面提高的,String,Integer等都是BootStrap ClassLoader加载的。

可以看到:

static class ExtClassLoader extends URLClassLoader
static class AppClassLoader extends URLClassLoader
public class URLClassLoader extends SecureClassLoader implements Closeable
public class SecureClassLoader extends ClassLoader

ExtClassLoader和AppClassLoader最终都继承自ClassLoader,ClassLoader这个类中定义类加载的一些基本方法,具体代码我们在介绍完它的实现原理之后说明

ClassLoader的类加载原理

jvm中的类加载原理主要是双亲委托机制


在当前需要加载某个类时,并不是当前类立马调用加载,而是向上委托,每个加载器都有自己的一个缓存,首先是App ClassLoader在自己的加载缓存中查找,没有找到就向Ext ClassLoader进行委托,Ext ClassLoader也在自己的加载缓存中查找,没有找到就向BootStrap ClassLoader进行委托,BootStrap ClassLoader也是在自己的加载缓存中查找,如果也没有找到,则回到这个加载类本身,它此时会自己去加载,如果还是没有加载到,则抛出ClassNotFoundException异常。如果找到了,就将其加载到内存中,并返回这个类在内存中的实例。整个过程是一个由下至上的委托过程,但是是一个由上至下的查找过程。

采用双亲委托模型的原因主要是杜绝重复加载,因为如果当前需要加载某个类时,自己就去加载,那么有可能父加载器已加载过,比如BootStrap ClassLoader在jvm启动的时候会加载String类,后面某个应用类需要使用String的时候,再次加载String类,则不合理。

那么jvm是怎么判断一个类有没有已经加载过的呢?这里除了判断class文件是一致的外,还需要是同一个类加载器加载的才行。比如App ClassLoader和某个自定义的类加载器都加载了同一个Test类,那这两个Test类不是同一个类型,jvm会认为它们是两个类。

执行代码:

public class Test {
	
	     public static void main(String[] args) {
	    	 ClassLoader cl = Test.class.getClassLoader();
	    	 
	    	 System.out.println(cl);
	    	 System.out.println(cl.getParent());
	    	 System.out.println(cl.getParent().getParent());
	    	 System.out.println(cl.getParent().getParent().getParent());
		}

}

打印出:

sun.misc.Launcher$AppClassLoader@14dad5dc
sun.misc.Launcher$ExtClassLoader@368102c8
null
Exception in thread "main" java.lang.NullPointerException
	at test.Test.main(Test.java:14)

第一行表示Test类是有AppClassLoader加载的

第二行表示Test类的父加载器,也就是AppClassLoader是有ExtClassLoader加载的

第三行表示ExtClassLoader的父加载器是null。之前已经提到,ExtClassLoader的父加载器是BootStrap ClassLoader,是jvm内核中的实现,所以这里显示为null。

参考推荐

classloader加载机制

深入分析Java ClassLoader原理

猜你喜欢

转载自blog.csdn.net/u014209205/article/details/80962069