一、文章来由

来阿里玩Java也有一个多月了,一直对Java虚拟机比较感兴趣,而ClassLoader是整个class载入过程中很重要的组件。而classloader有个双亲委派模型,师兄说这个模型不能破坏,于是打赌一试。

相信如果问:为什么要双亲委派,可能有人可以侃侃而谈,但是说到为什么要这么分层,为什么要分三层,如何绕过双亲委派模型。。。

这就不是那么容易了,这个时候就需要一些专研了。

二、classloader的作用

这个问题我问了师兄:加载+连接的所有过程,但是深入理解Java虚拟机说的不太一样(所以有待考证)

请原谅我贴图,但下面两张图字字珠玑(p228):

classloader虽然只用于实现类的加载动作,但在Java程序中作用却远远不限于类加载阶段,也就是后面说的可以决定类。

三、为什么要3个classloader

我个人认为有两个原因,当然可能不止。

1、是为了安全

http://stackoverflow.com/questions/28011224/what-is-the-reason-for-having-3-class-loaders-in-java

The reason for having the three basic class loaders (Bootstrap, extension, system) is mostly security.

A key concept is the fact that the JVM will not grant package access (the access that methods and fields have if you didn’t specifically mention private, public or protected) unless the class that asks for this access comes from the same class loader that loaded the class it wishes to access.

So, suppose a user calls his class java.lang.MyClass. Theoretically, it could get package access to all the fields and methods in the java.lang package and change the way they work. The language itself doesn’t prevent this. But the JVM will block this, because all the real java.lang classes were loaded by bootstrap class loader. Not the same loader = no access.

There are other security features built into the class loaders that make it hard to do certain types of hacking.

So why three class loaders? Because they represent three levels of trust. The classes that are most trusted are the core API classes. Next are installed extensions, and then classes that appear in the classpath, which means they are local to your machine.

For a more extended explanation, refer to Bill Venners’s “Inside the Java Virtual Machine”.

2、另外,这个帖子没有提到的应该是隔离

java的所有类都是由classloader加载的,不同classloader之间加载的类彼此是不可见的。tomcat加载了log4j,容器里servlet也加载了log4j,servlet是看不见tomcat加载的log4j类的,反之亦然。

在深入理解Java虚拟机,p278,提到:

tomcat为了支持权限目录结构,对目录中的类库进行加载和隔离,tomcat自定义了多个类加载器。

也说明了这点。

四、尝试绕过双亲委派

深入理解Java虚拟机,p231,写到:

双亲委派模型在jdk1.2引入,但它不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载方式。

换句话说,也就是可以不用这个模型的,自己实现类加载就可以,毕竟类加载器的原始作用就是:“通过类的全限定名得到类的二进制码流”

来看看双亲委派模型是如何实现的:

/*** Loads the class with the specified <a href="#name">binary name</a>.  The* default implementation of this method searches for classes in the* following order:** <ol>**   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class*   has already been loaded.  </p></li>**   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method*   on the parent class loader.  If the parent is <tt>null</tt> the class*   loader built-in to the virtual machine is used, instead.  </p></li>**   <li><p> Invoke the {@link #findClass(String)} method to find the*   class.  </p></li>** </ol>** <p> If the class was found using the above steps, and the* <tt>resolve</tt> flag is true, this method will then invoke the {@link* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.** <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link* #findClass(String)}, rather than this method.  </p>** <p> Unless overridden, this method synchronizes on the result of* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method* during the entire class loading process.** @param  name*         The <a href="#name">binary name</a> of the class** @param  resolve*         If <tt>true</tt> then resolve the class** @return  The resulting <tt>Class</tt> object** @throws  ClassNotFoundException*          If the class could not be found*/protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> 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 thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}

逻辑很清晰,优先给自己的parent加载器加载,特别注意,这里的父加载器,不是类的继承,因为三个classloader都是内存对象,所以他们只是逻辑上的父子关系。

(1)bootstrap classloader是native实现的

(2)extclassloader 和 APPclassloader 都是 URLclassloader 的子类对象

其实我想做的事情很简单,就是尝试自己写一个完全不依靠双亲委派的classloader,但是因为编码量比较大,所以我只尝试绕过APPclassloader,让自己的加载器继承(再次说明是逻辑上继承)于extclassloader

上面的代码已经显示,如果parent加载器没办法加载,就找子classloader的findclass方法,但是我想破坏这个模型,就必须重写classloader的loadclass方法

上代码:

package classloader;import java.io.*;/*** Created by hupo.wh on 2016/7/18.*/
public class OverwriteClassLoader extends ClassLoader {private String rootDir = "d:\\";public Class<?> loadClass(String name)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findClass(name);return c;}}private byte[] getClassData(String className) {//String path = classNameToPath(className);String path = "D:\\xiaohua\\WhTest\\target\\classes\\helloworld\\HelloWorld.class";try {InputStream ins = new FileInputStream(path);ByteArrayOutputStream baos = new ByteArrayOutputStream();int bufferSize = 4096;byte[] buffer = new byte[bufferSize];int bytesNumRead = 0;while ((bytesNumRead = ins.read(buffer)) != -1) {baos.write(buffer, 0, bytesNumRead);}return baos.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;}protected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = getClassData(name);if (classData == null) {throw new ClassNotFoundException();}else {System.out.println("name == "+name);return defineClass(name, classData, 0, classData.length);}}private String classNameToPath(String className) {return rootDir + File.separatorChar+ className.replace('.', File.separatorChar) + ".class";}public OverwriteClassLoader(ClassLoader classLoader) {super(classLoader);}protected final Class<?> whdefineClass(String name, byte[] b, int off, int len)throws ClassFormatError{return defineClass("helloworld.HelloWorld", b, off, len, null);}}/Main.java
class Main{public static void main(String[] args) throws ClassNotFoundException {ClassLoader extcl =new Object(){}.getClass().getEnclosingClass().getClassLoader();while(extcl.getParent()!=null){extcl=extcl.getParent();}System.out.println("extcl == "+extcl);System.out.println("overwriteclassloader == "+OverwriteClassLoader.class.getClassLoader());OverwriteClassLoader cl = new OverwriteClassLoader(extcl);Class<?> clazz = cl.loadClass("helloworld.HelloWorld");System.out.println(clazz.getClassLoader());}
}

然而我的程序止步于一个地方:

详细跟断点进去,发现这个地方本来我要加载,helloworld.HelloWorld类,但是加载java.lang.Object类的时候,一个native方法又调回了我的classloader,进入第三部分的第1小部分。

SecurityException: Prohibited package name: java.lang

  • 这又说明了一个问题,classloader去load这个类的父类,也是找我这个类,但是我这个类的loadclass没有双亲委派,同时安全检查又是用的classloader这个类内置的,所以通不过。

后来发现这段代码实际上是有问题的,因为我把InputStream写死了,下面代码才是正确的

其实这个时候,我自己加载的类已经绕过双亲委派了,因为自己这个类是没有去查父亲的,于是有了下面这个更极端的测试~~

package classloader;import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;/*** Created by hupo.wh on 2016/7/20.*/
public class ClassLoaderTest {public static void main(String[] args) throws Exception {ClassLoader myLoader = new ClassLoader() {@Overridepublic Class<?> loadClass(String name) {try {InputStream is = null;if(name == "helloworld.HelloWorld") {is = new FileInputStream("D:\\xiaohua\\WhTest\\target\\classes\\helloworld\\HelloWorld.class");}else {is = new FileInputStream("D:\\lang\\Object.class");//return super.loadClass(name);}byte [] b = new byte[is.available()];is.read(b);return defineClass(name,b,0,b.length);} catch (Exception e) {e.printStackTrace();}return null;}};Class<?> clazz = myLoader.loadClass("helloworld.HelloWorld");System.out.println(clazz.getClassLoader());}
}

我将rt.jar中的java.lang解压在d盘了,当然还是会报那个错误。。。

于是这样也会报错:

package java.lang;/*** Created by hupo.wh on 2016/7/20.*/
public class sayHello {public static void main(String[] args) {System.out.println("hello");}
}

当然也是有方法的,就是完全自己实现classloader这个类,不继承于任何东西,这样的话,jvm也拿你没办法了。

附上深入理解Java虚拟机正确运行源码(p228)

package classloader;import java.io.FileInputStream;
import java.io.InputStream;/*** Created by hupo.wh on 2016/7/20.*/
public class ClassLoaderTest2 {public static void main(String[] args) throws Exception {ClassLoader myLoader = new ClassLoader() {@Overridepublic Class<?> loadClass(String name) {try {String fileName = name.substring(name.lastIndexOf(".")+1)+".class";InputStream is = getClass().getResourceAsStream(fileName);if (is == null) {return super.loadClass(name);}byte [] b = new byte[is.available()];is.read(b);return defineClass(name,b,0,b.length);} catch (Exception e) {e.printStackTrace();}return null;}};Class<?> clazz = myLoader.loadClass("helloworld.HelloWorld");System.out.println(clazz.getClassLoader());Class<?> clazz1 = myLoader.loadClass("org.omg.CORBA.Any");System.out.println(clazz1.getClassLoader());}
}

参考资料

[1] 深入理解Java虚拟机

一次尝试绕过ClassLoader双亲委派的实验相关推荐

  1. java安全沙箱(一)之ClassLoader双亲委派机制

    java是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是: 类加载体系 .class文件检验器 内置于Java虚拟机(及语言)的安全特性 安全管理器及J ...

  2. java类加载-ClassLoader双亲委派机制

    "类加载体系"及ClassLoader双亲委派机制.java程序中的 .java文件编译完会生成 .class文件,而 .class文件就是通过被称为类加载器的ClassLoade ...

  3. 违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制

    转载自 违反ClassLoader双亲委派机制三部曲第二部--Tomcat类加载机制 前言: 本文是基于 ClassLoader双亲委派机制源码分析 了解过正统JDK类加载机制及其实现原理的基础上,进 ...

  4. 此计算机必须为委派而被信任_如何增强 ClassLoader 双亲委派模式 ?

    双亲委派模式 JVM加载类的实现方式,我们称为 双亲委托模型: 如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给自己的父加载器,每一层的类加载器都是如此,因此所 ...

  5. 双亲委派机制_史上三次破坏ClassLoader双亲委派机制

    人既会喜逢令人羡慕的幸运,也会遭遇始料不及的失败与磨难.艰难时,不丧失美好的希望:而在成功时,不忘记感恩之心.谦虚之心:人只要有这份心态,任何时候都可以重振旗鼓. 人的一生始于心,终于心. 本篇是继上 ...

  6. ClassLoader 双亲委派机制

    看了挺多双亲委派机制的博客,但是看完就容易忘掉,所以自己调试一下类加载的过程,并记录调试过程和结果. 1.定位类加载的方法,并打断点记录观察加载类SampleTomcatApplication.cla ...

  7. java 委托原则_为什么说 Java SPI 的设计违反双亲委派原则

    一.双亲委派模型 1.类加载器可以细分为如下三类 启动类加载器(Bootstrap ClassLoader),负责将所有存放在\lib目录中的,或者被-Xbootclasspath参数所指定路径中,并 ...

  8. JVM(1)之JVM的组成详解(字符串常量池+双亲委派机制+JIT即时编译......)

    以下总结自:<深入理解java虚拟机> + 宋红康老师视频 字节码文件介绍:深入理解JVM之Java字节码(.class)文件详解_Windy_729的博客-CSDN博客_字节码文件 JV ...

  9. java类加载机制为什么双亲委派_[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的...

    Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的不过源码其实比较简单,接下来简单介绍一下我们先从启动类说起有一个Launcher类 ...

最新文章

  1. 面试官问:为什么SpringBoot的 jar 可以直接运行?
  2. R语言进行主成分分析(PCA)、使用prcomp函数进行主成分分析:碎石图可视化(scree plot)、R通过线图(line plot)来可视化主成分分析的碎石图(scree plot)
  3. HTML4.01复习笔记一
  4. 干货+福利!MySQL常见的面试题+索引原理分析!
  5. shlve模块 序列化 python任意的数据
  6. 树控件单击获取到的节点信息不是当前选中的节点_常用基本控件测试用例(一)...
  7. TRIE - Data Structure
  8. linux挂载磁盘分区,Linux 新磁盘分区与挂载
  9. [html] 如何优化大数据列表(10万+)的性能?说说你的方案
  10. WINCE Driver 心得总结
  11. 基于Java+SpringBoot+vue+node.js实现自行车租赁平台管理系统
  12. 蓝点中文_linux2.0 实验二 简单shell命令
  13. 问题解决:./config.sh: line 103
  14. .net站点配置完后常见报错及解决措施
  15. Atitit 获取多媒体mp3 mp4 m4a元数据 G:\桌面安装\paidashi\bin\ffprobe.exe ffprobe -i 1.flv -print_format json -sh
  16. smb服务器速度测试_360路由器做smb服务器各种情况拷贝速度测试外加加装5g网卡...
  17. 微信小程序开通广告要求累计独立访客(UV)不低于 1000是什么意思?怎么查看UV数量?
  18. 2022年房地产市场趋势展望
  19. 阿星 centos7卸载mysql并且通过yum安装mysql
  20. 【世纪佳缘桌面V3.1.1正式版】聊天交流工具

热门文章

  1. IDEA setup 选择目录安装不了_在 Ubuntu 18.04 中安装几个常用软件(一)
  2. python读取雷达基数据_重磅更新!读取CINRAD雷达基数据的Python模块
  3. 软件或开发工具读取EXCEL文件报错ACEODBC.DLL驱动加载失败解决
  4. Windows安装pyav报错:ERROR: Failed building wheel for av.Failed to build av. ERROR: Could not build wheel
  5. Unity 编辑器代码打开场景
  6. 基于asp.net企业差旅管理系统-计算机毕业设计
  7. 11月区块链投融资事件回顾
  8. 电解电容使用久了电容量真的会下降吗?
  9. 验证码及验证码透明的背景
  10. 9.1 使用QPxmap类加载图片