JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制
1、类加载器站在Java虚拟机的角度看,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机、JDK8中),是虚拟机自身的一部分;另外一种是其他所有类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类 java.lang.ClassLoaderJDK8及以前版本中绝大多数程序
1、类加载器
站在Java虚拟机的角度看,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader)
,这个类加载器使用C++语言实现(HotSpot虚拟机、JDK8中),是虚拟机自身的一部分;另外一种是其他所有类加载器
,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类 java.lang.ClassLoader
JDK8及以前版本中绝大多数程序都会使用到以下3个系统提供的类加载器来进行加载
-
启动类(引导类)加载器:
负责加载支撑JVM运行的位于<JAVA_HOME>\lib目录下的核心类库
,而且是Java虚拟机能够识别的类库加载到虚拟机内存中
(如rt.jar、tools.jar、charsets.jar等,名字不符合的类库即使放到lib目录下也不会被加载)。 -
扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc. $ExtClassLoader中以Java代码的形式实现的。
负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所制定的路径中所有的类库
,是一种Java系统类库的扩展机制 -
应用程序类加载器(Application Class Loader):是由sun.misc.launcher$AppClassLoader来实现,由于应用程序加载器是ClassLoader类中getSystemClassLoader()方法的返回值,也称为系统类加载器。
负责加载用户类路径(ClassPath)上所有的类库
,如应用程序中没有默认自己的类加载器,则使用应用程序加载器为默认加载器。 -
自定义加载器:负责加载用户自定义路径下的类包
1.1、Launcher源码
package com.learn.jvm;
import com.sun.crypto.provider.DESKeyFactory;
/**
* @author liushiwei
*/
public class JvmClassLoader {
public static void main(String[] args) {
// 查看String的类加载器
System.out.println(String.class.getClassLoader());
// 查看DESKeyFactory扩展包中的加载器
System.out.println(DESKeyFactory.class.getClassLoader().getClass().getName());
// 查看当前类加载器
System.out.println(JvmClassLoader.class.getClassLoader().getClass().getName());
}
}
1.1.1、Launcher
sun.misc.Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候回准备应用程序运行中需要的类加载器。
Java命令执行代码的大体流程,结合图可以查看Launcher类的源码
类加载器是一个抽象类。给定类的二进制名称,类装入器应该尝试查找或生成构成该类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
2、双亲委派机制
双亲委派模型的工作过程是:如果一个类加载器收到了类加载请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层都是如此,因此所有加载请求最终都传送给顶层的启动类加载器,只有父加载器反馈自己无法加载这个加载请求时(它的搜索范围没有找到所需的类),子加载器才会尝试自己去完成加载
,双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
好处
-
具备优先级的层次关系,例如java.lang.Object类,它放在rt.jar之中,无论那个类加载器加载这个类,都会向上委派给模型的最顶端启动类加载器加载,因此Object类在程序的各个类加载器中都能保证是一个类,从而
保证被加载类的唯一性
-
这样便可以防止核心API库被随意篡改
,如自定义Object类package java.lang; public class Object { public static void main(String[] args) { System.out.println("Object"); } }
结果
2.1、ClassLoader源码
AppClassLoader的继承关系
应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 检查类是否已经加载
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
// 说明父类加载器无法完成加载请求
}
if (c == null) {
long t1 = System.nanoTime();
// 在父加载器无法加载时,在调用本身的findClass方法进行类加载,
// 最终调用的是URLClassLoader.findClass方法
// 真正加载类的逻辑
c = findClass(name);
// 这是定义类加载器; 记录统计
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
defineClass:经历验证、解析等
3、自定义类加载器
加载的类
public class JvmClassLoader1 {
public void getValue(){
System.out.println("JvmClassLoader1");
}
}
自定义加载器,及测试内部类
package com.learn.jvm;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath){
this.classPath= classPath;
}
private byte[] loadByte(String name) throws IOException {
String path = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + path.concat(".class"));
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
class MyClassLoaderDemo{
public static void main(String[] args) throws Exception {
// 初始化自定义类加载器,会先初始化父类ClassLoader,
// 其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
// D盘创建 demo/com/learn/jvm 几级目录,将JvmClassLoader类的复制类JvmClassLoader1.class丢入该目录
MyClassLoader myClassLoader = new MyClassLoader("D:/demo");
Class<?> aClass = myClassLoader.loadClass("com.learn.jvm.JvmClassLoader1");
// 通过反射调用类中方法
Object object = aClass.newInstance();
Method getValues = aClass.getDeclaredMethod("getValue", null);
getValues.invoke(object);
System.out.println(aClass.getClassLoader().getClass().getName());
}
}
- 执行结果:
-
如果当前项目路径下有JvmClassLoader1.class文件,则输出AppClassLoader应用程序加载器,因为自定义加载器的父加载器是AppClassLoader
-
如果当前项目路径下无JvmClassLoader1.class文件,则输出MyClassLoader
-
为什么自定义的类加载器的父加载器是AppClassLoader
- 因为初始化自定义类加载器时,会初始化父类ClassLoader,而ClassLoader的构造方法中,会给this.parent赋值,如下l.getClassLoader();中对应的类加载器就是AppClassLoader,满足双亲委派机制
4、打破双亲委派机制
例如在Tomact中部署多个项目,每个项目使用的相同但不用版本的组件
自定义类加载器,在加载类时,没有遵循双亲委派机制(先委托父加载器,父加载器没有此类,最后在交给子加载器加载),可以是自己先加载,加载不到在委托父加载器,或不需要父加载器加载。
package com.learn.jvm;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath){
this.classPath= classPath;
}
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 t1 = System.nanoTime();
if(name.contains("com.learn.jvm")){
// 自定义加载器加载类
c = findClass(name);
}else{
// 父加载器加载
c = this.getParent().loadClass(name);
}
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
private byte[] loadByte(String name) throws IOException {
String path = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + path.concat(".class"));
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
class MyClassLoaderDemo{
public static void main(String[] args) throws Exception {
// 初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
// D盘创建 demo/com/learn/jvm 几级目录,将JvmClassLoader类的复制类JvmClassLoader1.class丢入该目录
MyClassLoader myClassLoader = new MyClassLoader("D:/demo");
// 使用当前类加载器加载
ClassLoader classLoader = myClassLoader.getClass().getClassLoader();
Class<?> aClass = myClassLoader.loadClass("com.learn.jvm.JvmClassLoader1");
// 通过反射调用类中方法
Object object = aClass.newInstance();
Method getValues = aClass.getDeclaredMethod("getValue", null);
getValues.invoke(object);
System.out.println(aClass.getClassLoader().getClass().getName());
}
}
更多推荐
所有评论(0)