前言

曾经有一次,面试官问到类加载机制,相信大多数小伙伴都可以答上来双亲委派机制,也都知道JVM出于安全性的考虑,全限定类名相同的String是不能被加载的。但是如果加载了,会出现什么样的结果呢?异常?那是什么样的异常。如果包名不相同呢?自定义类加载器是否可以加载呢?相信面试官从各种不同的角度出击,很快就会答出漏洞,毕竟咱没有深入研究过虚拟机...

接下来笔者就针对上述问题进行一一验证。该篇文章抱着求证答案的方向出发,并无太多理论方面的详解。如有理解上的偏差,还望大家不吝赐教。

JVM都有哪些类加载器

首先我们放上一张节选自网络的JVM类加载机制示意图

JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader:

  • BootstrapClassLoader(启动类加载器) :最顶层的加载类,由C++实现,负责加载 %JAVA_HOME%/lib目录下的jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类。
  • ExtensionClassLoader(扩展类加载器) :主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。
  • AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。

JVM类加载方式

类加载有三种方式:

  • 1、命令行启动应用时候由JVM初始化加载
  • 2、通过Class.forName()方法动态加载
  • 3、通过ClassLoader.loadClass()方法动态加载

Class.forName()和ClassLoader.loadClass()区别

  • Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
  • ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
  • Class.forName(name,initialize,loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

JVM类加载机制

  • 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
  • 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  • 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

JVM类加载机制源码

双亲委派模型实现源码分析

private final ClassLoader parent;
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) {//父加载器不为空,调用父加载器loadClass()方法处理c = parent.loadClass(name, false);} else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//抛出异常说明父类加载器无法完成加载请求}if (c == null) {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;}}

双亲委派模型的好处

双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

如果我们不想用双亲委派模型怎么办?

为了避免双亲委托机制,我们可以自己定义一个类加载器,然后重写 loadClass() 即可。

系统类加载器加载自定义String

1. 首先我们看下普通的类加载过程

package com.example.demojava.loadclass;
public class ClassLoaderDemo{public static void main(String[] args) {System.out.println("ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader());System.out.println("The Parent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent());System.out.println("The GrandParent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent());}
}

结果输出

ClassLodarDemo's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
The Parent of ClassLodarDemo's ClassLoader is sun.misc.Launcher$ExtClassLoader@75bd9247
The GrandParent of ClassLodarDemo's ClassLoader is null

AppClassLoader的父类加载器为ExtClassLoaderExtClassLoader的父类加载器为null,null并不代表ExtClassLoader没有父类加载器,而是 BootstrapClassLoader

2. 我们自己定义一个String类,看下会发生什么

package com.example.demojava.loadclass;public class String {public static void main(String[] args) {System.out.println("我是自定义的String");}
}

结果输出

➜  demo-java javac src/main/java/com/example/demojava/loadclass/String.java
➜  demo-java java src.main.java.com.example.demojava.loadclass.String
错误: 找不到或无法加载主类 src.main.java.com.example.demojava.loadclass.String

这里分明有main方法,全限定类名又和jdk的String不在同一个package(不会造成冲突),为什么会输出找不到或无法加载主类呢?

细心的小伙伴一定会发现该类'没有导入'系统的String类,会不会因为JVM的类加载机制,AppClassLoader加载类的时候,由于自定义的String被加载,拦截了上层的String类呢?String对象是自定义的,不符合main()方法的定义方式,故系统抛找不到main()方法。

我们反过来验证下刚才的推测,再次运行刚才的ClassLoaderDemo会发生什么呢?what?IDE中的main()方法去哪里了?还是手动编译运行下吧

➜  demo-java javac src/main/java/com/example/demojava/loadclass/ClassLoaderDemo.java
➜  demo-java java src.main.java.com.example.demojava.loadclass.ClassLoaderDemo
错误: 找不到或无法加载主类 src.main.java.com.example.demojava.loadclass.ClassLoaderDemo

结果显示: 之前正常运行的java类也找不到主类了。

我们导入正确的String类再来验证下

package com.example.demojava.loadclass;public class String {public static void main(java.lang.String[] args) {System.out.println("我是自定义的String");}
}

结果输出

我是自定义的String

3. 能否覆写lang包下的String类?

上边的案例修改包路径即可

package java.lang;public class String {public static void main(java.lang.String[] args) {System.out.println("我是自定义的String");}
}

输出报错

Connected to the target VM, address: '127.0.0.1:63569', transport: 'socket'
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

分析:首先由于全限定类名java.lang.String等于jdk中的String类,根据上边类加载源码可知,当AppClassLoader加载该String时,判断java.lang.String已经加载,便不会再次加载。所以执行的依旧是jdk中的String,但是系统的java.lang.String中没有main()方法,所以会报错。这是一种安全机制。

然后验证下默认的类加载器能否加载自定义的java.lang.String。==,默认的AppClassLoader能加载Everything?

public class LoadStringDemo {public static void main(String[] args) {URLClassLoader systemClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();URL[] urLs = systemClassLoader.getURLs();for (URL url: urLs) {System.out.println(url);}}
}

输出日志如下

...
file:/Users/cuishiying/work/demo-java/target/classes/
...

日志太多,但是绝对没有其他的包路径(当前包下的java.lang.String默认只能时jdk中的)

自定义类加载器

为什么会存在自定义类加载器呢

自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。

因为实际项目中,会有多种加载.class文件的方式,

  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip,jar等归档文件中加载.class文件
  • 从专有数据库中提取.class文件
  • 将Java源文件动态编译为.class文件

如何自定义类加载器

package com.example.demojava.loadclass;import com.demo.ClassLoaderDemo;import java.io.*;
import java.lang.reflect.Method;public class MyClassLoader extends ClassLoader {private String root;/*** @param name 全限定类名* @return* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();} else {return defineClass(name, classData, 0, classData.length);}}private byte[] loadClassData(String className) {String fileName = root + File.separatorChar +className.replace('.', File.separatorChar) + ".class";try {InputStream ins = new FileInputStream(fileName);ByteArrayOutputStream baos = new ByteArrayOutputStream();int bufferSize = 1024;byte[] buffer = new byte[bufferSize];int length = 0;while ((length = ins.read(buffer)) != -1) {baos.write(buffer, 0, length);}return baos.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;}public String getRoot() {return root;}public void setRoot(String root) {this.root = root;}public static void main(String[] args) throws Exception {MyClassLoader classLoader = new MyClassLoader();classLoader.setRoot("/Users/cuishiying/Desktop/demo");Class<?> clz = Class.forName("LoadDemo", true, classLoader);Object  instance = clz.newInstance();Method test = clz.getDeclaredMethod("test");test.setAccessible(true);test.invoke(instance); System.out.println(instance.getClass().getClassLoader());}
}

结果输出

test
com.example.demojava.loadclass.MyClassLoader@75bd9247

由此可知,自定义类加载器已可以正常工作。这里我们不能把LoadDemo放在类路径下,由于双亲委托机制的存在,会直接导致该类由 AppClassLoader加载,而不会通过我们自定义类加载器来加载。

自定义类加载器加载手写java.lang.String

改写自定义类加载器的main()方法

public static void main(String[] args) throws Exception {MyClassLoader classLoader = new MyClassLoader();classLoader.setRoot("/Users/cuishiying/Desktop/demo");Class<?> clz = classLoader.findClass("java.lang.String");Object  instance = clz.newInstance();System.out.println(instance.getClass().getClassLoader());}

JVM由于安全机制抛出了SecurityException

/Users/cuishiying/Desktop/demo/java/lang/String.class
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.langat java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)at java.lang.ClassLoader.defineClass(ClassLoader.java:761)at java.lang.ClassLoader.defineClass(ClassLoader.java:642)at com.example.demojava.loadclass.MyClassLoader.findClass(MyClassLoader.java:25)at com.example.demojava.loadclass.MyClassLoader.main(MyClassLoader.java:71)

以上内容都是我自己的一些感想,分享出来欢迎大家指正,顺便求一波关注,有想法的伙伴可以评论或者私信我哦~

作者:当我遇上你csy
出处:https://www.cnblogs.com/idea360/p/12377464.html

c 自定义实现string类 clear_JVM类加载器是否可以加载自定义的String相关推荐

  1. 查看类的实现类mac_自定义类加载器实现热加载

    一.实现的需求 当一个java被修改后,系统无需重启,替换的类立即生效,这里以Work.java为例 二.实现的思路 1)难点分析 当项目在执行的时候,我们修改了Work.java,这个类的class ...

  2. 【Android 插件化】“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  3. 16.不同类加载器作用与加载动作分析---(获取ClassLoader的途径)

    1. 打印类加载器 2. 通过应用类加载器来获取指定当前项目的文件的全路径 因为应用类加载器可以加载当前应用的类,所以我们可以通过它的getResource方法来获取这个类的全路径 package c ...

  4. 自定义类加载器_jvm超详细探索自定义类加载器(值得收藏)

    原创:鲁班学院子牙老师 微信公众号搜索启明南 如果你想看懂本篇文章,需要你对类加载器有一定的了解.如JVM自带的类加载器.双亲委派.自定义类加载器.类加载每个阶段做了什么--如果你对这些知识还有夹生的 ...

  5. class没有发布到tomcat_Java 类在 Tomcat 中是如何加载的?

    作者 :xingoo https://www.cnblogs.com/xing901022/p/4574961.html 说到本篇的Tomcat类加载机制,不得不说翻译学习Tomcat的初衷. 之前实 ...

  6. Java 类在 Tomcat 中是如何加载的?

    说到本篇的Tomcat类加载机制,不得不说翻译学习Tomcat的初衷. 之前实习的时候学习JavaMelody的源码,但是它是一个Maven的项目,与我们自己的Web项目整合后无法直接断点调试. 后来 ...

  7. 多项目加载顺序修改_面试官:Java 类在 Tomcat 中是如何加载的?

    说到本篇的Tomcat类加载机制,不得不说翻译学习Tomcat的初衷. 之前实习的时候学习JavaMelody的源码,但是它是一个Maven的项目,与我们自己的Web项目整合后无法直接断点调试. 后来 ...

  8. 前端加载自定义字体及速度优化

    今天是2.14情人节,也是另一个重要的日子,那就是我的第一个全栈项目上线啦~~~ www.daren.com 这个是公司的官网,采用Python+Django做后端,前端也用了gulp自动化工作流,使 ...

  9. java 根据类路径获取类_Java类加载器

    类加载器 Java程序可以通过类加载器来达到通过一个类的全限定类名来获取该类的二进制字节流. 类与类加载器 对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性 ...

  10. matlab 定义string_MATLAB开发自动驾驶第十课-创建用于加载自定义地面真值数据源的类...

    在Ground Truth Labeler应用中,可以对来自图像和点云数据源的信号进行标记.这些数据源包括视频.图像序列.点云序列.Velodyne 数据包捕获.视频.图像和点云数据. (PCAP)文 ...

最新文章

  1. AI算法工程师之路 梯度下降百度总结 练习机器学习的网站
  2. centos7.3上yum install nodejs
  3. PHP的getimagesize获取图像信息
  4. jenkins 通过批处理自动构建 非标准项目
  5. react 手指移开_代码简介:React的五个死亡手指
  6. LeetCode 327. 区间和的个数(multiset二分查找/归并排序)
  7. php 网络图片 转本地,PHP将Base64图片转换为本地图片并保存
  8. mysql通过日志恢复数据_mysql通过binlog日志来恢复数据
  9. 华为交换机命令 端口速率_华为交换机限速配置命令2016
  10. linux fdisk 4k,linux查看硬盘4K对齐方法
  11. 11.深入分布式缓存:从原理到实践 --- Aerospike原理及广告业务应用
  12. 数据库如何执行sql语句
  13. 将长方形转换为正方形
  14. 激光导航技术是如何实现的?
  15. 算力测试Linux,附录:计算力的标准Linpack测试详细指南(1)
  16. PCIe系列专题之二:2.5 Flow Control缓存架构及信用积分
  17. 100个人检测一个,最少需要多少试管
  18. windows关闭端口
  19. 软考高项 : (1)论信息系统项目的人力资源管理
  20. 卸载软件蓝屏报错0x0000003B

热门文章

  1. css 跳转电脑分辨率
  2. 二 、 搭建Android 开发环境读书笔记
  3. Oracle ERP Interface堵住--Request Running too long time,查找Request执行的Sql
  4. PHP处理多表查询时的SQL语句拆分与重新组装
  5. 15条SQLite3语句
  6. ASCII、Unicode、UTF、base64
  7. ylbtech-dbs:ylbtech-7,welfareSystem(福利发放系统)
  8. Redis的安装和使用之二------phpredis与phpRedisAdmin
  9. [转载]AxureRP学习成长之路
  10. 1. 英语邮件中经常用到的单句 (每次10句)