Java中的类加载器分为两种,一种Java 自带的类加载器,另一种是用户自定义的类加载器
将分配好的字节码文件分配给内存时,需要使用到类加载器。如图所示
Java自带的类加载器分为三种
一、启动类加载器(根类加载器)
负责加载:$JAVA_HOME 中jre/lib/rt.jar里所有的class(由Sun公司写好的类,如String,File等),由C++实现,不是ClassLoader子类,所以启动类加载器没有父类加载器,它没有父类加载器。但是它是扩展类加载器的父类加载器
public static void main(String[] args) throws ClassNotFoundException {
// 获取根类加载器,此结果的输出是null,因为在java中,
// 默认使用返回null代表使用根类加载器
Class<?> clazz = Class.forName("java.lang.String");
System.out.println(clazz.getClassLoader());
}
二、扩展类加载器
负责加载:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包(加载的都是jdk内部自己使用的类。),它的父类加载器是启动类加载器,但是你要验证的话,他会输出为null,也就是说在java类加载器中,null代表了启动类加载器。
三、应用类加载器(系统类加载器)
负责加载:负责加载classpath中指定的jar包及目录下class。(包括程序员编写的类)。它的父类加载器是扩展类加载器。
public static void main(String[] args) throws ClassNotFoundException {
// 获取应用类加载器,此结果输出的是
// sun.misc.Launcher$AppClassLoader@14dad5dc,
// 代表的是加载该类的是应用类加载器
Class<?> clazz2 = Class.forName("com.jvm.dlassLoader.C");
System.out.println(clazz2.getClassLoader());
}
类加载机制
接下来,看一下什么是双亲委托机制
双亲委托机制
定义:简单的说,当一个类需要加载时,类加载器总是先去让父类加载器去进行加载此类,如果父加载器可以完成类加载任务,就成功返回;当父类加载器不能加载此类时,则该类加载进行加载。
好处:
- 可以确保Java核心库的类型安全:所有的Java应用都至少会引用java.lang.object类,也就是所在运行期,java.lang.Object这个类会被加载到Java虚拟机中;如果这个加载过程是由Java应用自己的类加载器所完成的,那么很可能就会在JVM中存在多个版本的java.lang.Object类,而且这些类之间还是不兼容的,相互不可见的(正是命名空间在发挥着作用)。借助于双亲委托机制,Java核心类库中的类的加载工作都是由启动类加载器来统一完成,从而确保了Java应用所使用的都是同一个版本的额java核心类库,他们之间是相互兼容的。
- 可以确保Java核心类库所提供的类不会被自定义的类所替代
- 不同的类加载器可以为相同名称的类创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器来加载他们即可。不同类加载器所加载的类之间是不兼容的忙着就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间,这种技术在很多框架中得到了实际应用。
打破双亲委托机制
1. 为什么要打破
个人认为:因为子类加载器可以使用父类加载器所加载的类(简单地说就是能看到父类加载器加载的类),并且越基础的类就越由上层的加载器加载,所以这就保证了我们可以放心的调用他们的基础类(Api)。但是,会有特殊情况。例如:
一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,
它的代码由启动类加载器去加载(在JDK1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者的代码,但启动类加载器不可能“认识”这些代码。
为了解决这个问题,Java 引用了线程上下文类加载器。