同步操作将从 flatfish/Java-Review 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
蚂蚁金服:
深入分析 ClassLoader,双亲委派机制
类加载器的双亲委派模型是什么?
一面:双亲委派机制及使用原因
百度:
都有哪些类加载器,这些类加载器都加载哪些文件?
手写一个类加载器 Demo
Class 的 forName("java.lang.String") 和 Class 的 getClassLoader() 的 loadClass("java.lang.String")有什么区别?
腾讯:
什么是双亲委派模型?
类加载器有哪些?
小米:
滴滴:
简单说说你了解的类加载器
一面:讲一下双亲委派模型,以及其优点
字节跳动:
京东:
类加载器的双亲委派模型是什么?
双亲委派机制可以打破吗?为什么?
class ClassLoader {
ClassLoader parent; //父类加载器
public ClassLoader(ClassLoader parent) {
this.parent = parent;
}
}
class ParentClassLoader extends ClassLoader {
public ParentClassLoader(ClassLoader parent) {
super(parent);
}
}
class ChildClassLoader extends ClassLoader {
public ChildClassLoader(ClassLoader parent) {
//parent = new ParentClassLoader();
super(parent);
}
}
使用 -XX:+TraceClassLoading 参数得到
启动类加载器使用C++编写的?对的!
C/C++:指针函数和函数指针、C++支持多继承、更加高效
Java:由C++演变而来,(C++)-- 版本,单继承
每个 Class 对象都会包含一个定义它的 ClassLoader 的一个引用
获取 ClassLoader 的途径
获取当前类的 ClassLoader
clazz.getClassLoader();
获得当前线程上下文的 ClassLoader
Thread.currentThread().getContextClassLoader();
获得系统的 ClassLoader
ClassLoader.getSystemClassLoader();
说明:
站在程序的角度看,引导类加载器与另外两种类加载器(系统类加载器和扩展类加载器)并不是同一个层次意义上的加载器,引导类加载器是使用 C++ 语言编写而成的,而另外两种类加载器则是使用 Java 语言编写的。由于引导类加载器压根儿就不是一个 Java 类,因此在 Java 程序中只能打印出空值
数组类的 Class 对象,不是由类加载器去创建的,而是在 Java 运行期 JVM 根据需要自动创建的。对于数组类的类加载器来说,是通过 Class.geetClassLoader() 返回的,与数组当中元素类型的类加载器是一样的:如果数组当中的元素类型是基本数据类型,数组类是没有类加载器的
String[] strArr = new String[6];
System.out.println(strArr.getClass().getClassLoader());
//运行结果:null
ClassLoaderTest[] test = new ClassLoaderTest[1];
System.out.println(test.getClass().getClassLoader());
//运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2
int[] inst = new int[2];
System.out.println(inst.getClass().getClassLoader());
//运行结果:null
抽象类 ClassLoader 的主要方法:(内部没有抽象方法)
public final ClassLoader getParent()
public Class<?> loadClass(String name) throws ClassNotFoundException
protected Class<?> findClass(String name) throws ClassNotFoundException
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
简单举例:
protected Class<?> findClass(String name) throws ClassNotFoundException {
//获取类的字节数组
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
//使用 defineClass 生成 Class 对象
return defineClass(name, classData, 0, classData.length);
}
}
protected final void resolveClass(Class<?> c)
protected final Class<?> findLoadedClass(String name)
private final ClassLoader parent;
测试代码:
ClassLoader.getSystemClassLoader().loadClass("cn.icanci.java.User")
涉及到对如下方法的调用:
// resolve:true 的时候,加载class的时候同时进行解析操作
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 {
// 如果父类加载器不为null
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
}
// 当前类的加载器的父类加载器为加载此类 or 当前类加载器未加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 调用当前ClassLoader的findClass() 方法
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;
}
}
接着 SecureClassLoader 扩展了 ClassLoader,新增了几个与使用相关的代码源(对代码源的位置及其证书的验证)和权限定义类验证(主要针对 Class 源码的访问权限)的方法,一般我们不会直接跟这个类打交道,更多的是与它的子类 URLClassLoader 有所关联
前面说过,ClassLoader 是一个抽象类,很多方法是空的没有实现,比如 findClass()、findResource() 等。而 URLClassLoader 这个实现类为这些方法提供了具体的实现。并新增了 URLClassPath 类协助取得 Class 字节码流等功能。在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承 URLClassLoader 类,这样就可以避免自己去编写 findClass() 方法及其获取字节码流的方式,使自定义类加载器编写更加简洁
Class.forName():是一个静态方法,最常用的是 Class.forName(String className);根据传入的类的权限定名返回一个 Class 对象。**该方法在将 Class 文件加载到内存的同时,会执行类的初始化。**如:Class.forName("cn.icanci.java.HelloWorld");
ClassLoader.loadClass() 这是一个实例方法,需要一个 ClassLoader 对象来调用该方法。该方法将 Class 文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化。该方法因为需要得到一个 ClassLoader 对象,所以可以根据需要指定使用哪个类加载器,如:ClassLoader c1 = .....; c1.loadClass("cn.icanci.java.HelloWorld");
类加载器用来把类加载到 Java 虚拟机中。从 JDK 1.2 版本开始,类的加载过程采用双亲委派机制,这种机制能更好地保证 Java 平台的安全
定义
本质
优势
避免类的重复加载,确保一个类的全局唯一性
Java 类随着它的类加载器一起具备了一种带有优先级的层级关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子 ClassLoader 再加载一次
保护程序安全,防止核心 API 被随意篡改
代码支持
举例
思考
双亲委派模式的弊端
结论
破坏双亲委派机制1
双亲委派模型并不是一个具有强制性约束的模型,而是 Java 设计者推荐给开发者们的类加载器实现方式
在 Java 的世界中大部分的类加载器都遵循这个模型,但也有例外情况,直到 Java 模块化出现为止,双亲委派模型主要出现过3次较大规模"被破坏"的情况
第一次破坏双亲委派机制:
双亲委派模型的第一次"被破坏"其实发生在双亲委派模型出现之前——即 JDK 1.2 面世以前的"远古"时代
由于双亲委派模型在 JDK 1.2 之后才被引入,但是类加载器的概念和抽象类 java.lang.ClassLoader 则在 Java 的第一个版本中就已经存在,面对已经存在的用户自定义类加载器的代码,Java 设计者们引入双亲委派模型时不得不做出一些妥协,为了兼容这些已有的代码,无法再以技术手段避免 loadClass() 被子类覆盖的可能性,只能在 JDK 1.2 之后的 java.lang.ClassLoader 中添加一个新的 protected 方法 findClass(),并引导用户编写的类加载逻辑时尽可能去重写这个方法,而不是在 loadClass() 中编写代码。上节我们已经分析过 loadClass() 方法,双亲委派的具体逻辑就实现在这里面,按照 loadClass() 方法的逻辑,如果父类加载失败,会自动调用自己的 findClass() 方法来完成加载,这样既不影响用户按照自己的意愿去加载类,又可以保证新写出来的类加载器是符合双亲委派规则的
破坏双亲委派机制2
破坏双亲委派机制3
第三次破坏双亲委派机制:
双亲委派模型的第三次"被破坏"是由于用户对程序动态性的追求而导致的。如:代码热替换(Hot Swap)、**模块热部署(Hot Deployment)**等
IBM 公司主导的 JSR-291(即 OSGI R4.2)实现模块化热部署的关键是它自定义的类加载器机制的实现,每个程序模块(OSGI 中称为 Bundle)都有一个自己的类加载器,当需要更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换。在 OSGI 环境下,类加载器不再双亲委派模型推荐的树状结构,而是进一步发展为更加复杂的网状结构
当收到类加载请求时,OSGI 将按照下面的顺序进行类搜索:
说明:只有开头两点仍然符合双亲委派模型的原则,其余的类查找都是在平级的类加载器中进行的
小结:
为什么要自定义类加载器?
修改类加载的方式
扩展加载源
防止源码泄露
常见的场景
注意
用户通过定制自己的类加载器,这样可以重新定义类的加载规则,以便实现一些自定义的处理逻辑
实现方式
Java 提供了抽象类 java.lang.ClassLoader,所有用户自定义的类加载器都应该继承 ClassLoader 类
在自定义 ClassLoader 的子类时候,我们常见的会有两种做法:
对比
loadClass() 这个方法是实现双亲委派模型逻辑的地方,擅自修改这个方法会导致模型被破坏,容易造成问题。因此我们最好是在双亲委派模型框架内进行小范围的改动,不破坏原有的稳定结构。同时,也避免了自己重写 loadClass() 方法的过程中必须写双亲委托的重复代码,从代码的复用性来看,不直接修改这个方法始终是比较好的选择
当编写好自定义类加载器后,便可以在程序中调用 loadClass() 方法来实现类加载操作
说明
package cn.icanci.classloader;
import java.io.*;
/**
* @author: icanci
* @date: Created in 2020/11/25 22:55
* 自定义ClassLoader
*/
@SuppressWarnings("all")
public class MyClassLoader extends ClassLoader {
private String path;
public MyClassLoader(String path) {
this.path = path;
}
public MyClassLoader(ClassLoader parent, String path) {
super(parent);
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
BufferedInputStream bis = null;
ByteArrayOutputStream baos = null;
try {
// 获取字节码完整路径
String fileName = path + name + ".class";
// 获取输入流
bis = new BufferedInputStream(new FileInputStream(fileName));
baos = new ByteArrayOutputStream();
int len;
byte[] data = new byte[1024];
while ((len = bis.read(data)) != -1) {
baos.write(data, 0, len);
}
// 转化为二进制
byte[] bytes = baos.toByteArray();
// 返回 Class
Class<?> c = defineClass(null, bytes, 0, bytes.length);
return c;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != baos) {
baos.close();
}
if (null != bis) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
package cn.icanci.classloader;
/**
* @author: icanci
* @date: Created in 2020/11/25 23:01
*/
@SuppressWarnings("all")
public class MyClassLoaderTest {
private static String path = "E:\\NewIdeaHome\\IcanciHome\\JVM-Demo\\out\\production\\JVM-Demo\\cn\\icanci\\classloader\\";
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader myClassLoader = new MyClassLoader(path);
Class<?> demo1 = myClassLoader.loadClass("Demo1");
System.out.println(demo1.getClassLoader().getClass().getName());
System.out.println(demo1.getClassLoader().getParent());
}
}
// 输出
cn.icanci.classloader.MyClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
如果有程序直接依赖了这种继承关系,或者依赖了 URLClassLoader 类的特定方法,那代码很可能会在 JDK 9 及更高版本的 JDK 中崩溃
在 Java 9 中,类加载器有了名称。该名称在构造方法中指定,可以通过 getName() 方法来获取。平台类加载器的名称是 Platform,应用类加载器的名称是 App。类加载器的名称在调试与类加载器相关的问题时会非常有用
启动类加载器现在是在 JVM 内部和 Java 类库共同协作实现的类加载器(以前是 C++ 实现),但为了与之前代码兼容,在获取启动类加载器的场景中仍然会返回 null,而不会得到 BootClassLoader 实例
类加载的委派关系也发生了变动
当平台及应用程序类加载器收到类加载请求,在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责哪个模块的加载器完成加载
双亲委派模式示意图 (左侧是JDK9之前的双亲委派机制,JDK9之后的双亲委派机制)
附加:
启动类加载器负责加载的模块
java.base java.security.sasl
java.datatransfer java.xml
java.desktop jdk.httpserver
java.instrument jdk.internal.vm.ci
java.logging jdk.management
java.management jdk.management.agent
java.management.rmi jdk.naming.rmi
java.naming jdk.net
java.prefs jdk.sctp
java.rmi jdk.unsupported
java.activation* jdk.accessibility
java.compiler* jdk.charsets
java.corba* jdk.crypto.cryptoki
java.scripting jdk.crypto.ec
java.se jdk.dynalink
java.se.se jdk.incubator.httpclient
java.security.jgss jdk.internal.vm.compiler*
java.smartcardio jdk.jsobject
java.sql jdk.localedata
java.sql.rowset jdk.naming.dns
java.transaction* jdk.scripting.nashorn
java.xml.bind* jdk.security.auth
java.xml.crypto jdk.security.jgss
java.xml.ws* jdk.xml.dom
java.xml.ws.annotation* jdk.zipfs
jdk.aot jdk.jdeps
jdk.attach jdk.jdi
jdk.compiler jdk.jdwp.agent
jdk.editpad jdk.jlink
jdk.hotspot.agent jdk.jshell
jdk.internal.ed jdk.jstatd
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。