首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制[通俗易懂]

JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制[通俗易懂]

作者头像
全栈程序员站长
发布2022-10-03 13:21:24
发布2022-10-03 13:21:24
38400
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

1、类加载器

站在Java虚拟机的角度看,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机、JDK8中),是虚拟机自身的一部分;另外一种是其他所有类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类 java.lang.ClassLoader

JDK8及以前版本中绝大多数程序都会使用到以下3个系统提供的类加载器来进行加载

  1. 启动类(引导类)加载器:负责加载支撑JVM运行的位于<JAVA_HOME>\lib目录下的核心类库,而且是Java虚拟机能够识别的类库加载到虚拟机内存中(如rt.jar、tools.jar、charsets.jar等,名字不符合的类库即使放到lib目录下也不会被加载)。
  2. 扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc. $ExtClassLoader中以Java代码的形式实现的。负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所制定的路径中所有的类库,是一种Java系统类库的扩展机制
  3. 应用程序类加载器(Application Class Loader):是由sun.misc.launcher$AppClassLoader来实现,由于应用程序加载器是ClassLoader类中getSystemClassLoader()方法的返回值,也称为系统类加载器。负责加载用户类路径(ClassPath)上所有的类库,如应用程序中没有默认自己的类加载器,则使用应用程序加载器为默认加载器。
  4. 自定义加载器:负责加载用户自定义路径下的类包

1.1、Launcher源码

代码语言:javascript
代码运行次数:0
运行
复制
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类

代码语言:javascript
代码运行次数:0
运行
复制
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方法,该方法的大体逻辑如下

代码语言:javascript
代码运行次数:0
运行
复制
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、自定义类加载器

加载的类

代码语言:javascript
代码运行次数:0
运行
复制
public class JvmClassLoader1 { 

public void getValue(){ 

System.out.println("JvmClassLoader1");
}
}

自定义加载器,及测试内部类

代码语言:javascript
代码运行次数:0
运行
复制
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中部署多个项目,每个项目使用的相同但不用版本的组件

自定义类加载器,在加载类时,没有遵循双亲委派机制(先委托父加载器,父加载器没有此类,最后在交给子加载器加载),可以是自己先加载,加载不到在委托父加载器,或不需要父加载器加载。

代码语言:javascript
代码运行次数:0
运行
复制
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());
}
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/197533.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年9月4日 上,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、类加载器
    • 1.1、Launcher源码
      • 1.1.1、Launcher
  • 2、双亲委派机制
    • 2.1、ClassLoader源码
  • 3、自定义类加载器
  • 4、打破双亲委派机制
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档