Java 类加载器
1). 字节码和.class文件区别
新建Java对象时,JVM将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath目录下(及Java工程的bin目录下)的.class文件中,类加载需要将.class文件导入硬盘,经过处理变成字节码加载到内存。
示例:
public class ClassLoaderTest { public static void main(String[] args) { // 输出ClassLoaderTest的类加载器名称 System.out.println("ClassLoaderTest类的类加载器名称:" + ClassLoaderTest.class.getClassLoader().getClass().getName()); System.out.println("System类的类加载器名称:" + System.class.getClassLoader()); System.out.println("List类的类加载器名称:" + List.class.getClassLoader()); // 获取ClassLoaderTest类的类加载器 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); while (null != classLoader) { System.out.println(classLoader.getClass().getName() + "->"); // 获取类加载器的父类 classLoader = classLoader.getParent(); } System.out.println(classLoader); } }
打印结果:
从打印结果可以看出ClassLoaderTest类是有AppClassLoader加载的。
因为System类,List,Map等这样的系统提供jar类都在rt.jar中,所以由BootStrap类加载器加载,因为BootStrap是祖先类,不是Java编写的,所以打印出class为null。 详情请看第三点:类加载器的委托机制
2). Java虚拟中的类加载器
Java虚拟机中可以安装多个类加载器,系统默认的有三个,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
由于类加载器本身是Java类,也需要被类加载器加载,因此必须有一个为类加载器不是Java类,因此Bootstrap是由C/C++代码编写,被封装到JVM内核中,ExtClassLoader和AppClassLoader是Java类。
类加载器属性结构图:
3). 类加载器的委托机制
问题:Java虚拟机要加载第一个类的时候,使用哪一个类加载器加载?
(1). 当前线程的类加载器加载线程中的第一个类(当前线程的类加载器:Thread类中有一个get/setContextClassLoader(ClassLoader);方法,可以获取/指定本线程中的类加载器)
(2). 如果类A引用了类B,Java虚拟机将使用加载类A的加载器来加载类B
(3). 可以直接调用ClassLoader.loadClass(String className)方法来指定某个类加载器去加载某个类
每个类加载器加载类时,先委托其上级类加载器来加载,当所有父加载器没有加载到类时,回到发起者类加载器,如果还是加载不了,则会抛出ClassNotFoundException。
好处:
能够很好的统一管理类加载,首先交给上级,如果上级有,就加载,这样如果之前已经加载过的类,这时候在来加载它的时候只要拿过来用就可以了,无需二次加载
4). 测试ExtClassLoader
- 将ClassLoaderTest类打包为Jar文件,拷贝至
jdk\jre\lib\ext\下
路径
图3.png - 接下来在eclipse中运行ClassLoaderTest文件,其打印结果为
图4.png
打印结果验证了类加载器的委托机制.
测试完成后记得将test.jar删除
5). 类加载器主要方法
- 默认的类加载器
System.out.println("默认的类加载器:"+ClassLoader.getSystemClassLoader());
- loadClass(String name) 源码
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 加锁,进行同步处理,可能多个线程加载类 synchronized (getClassLoadingLock(name)) { // 检查是否已经加载过 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 如果自定义的类加载器的parent不为null,就调用parent的loadClass进行类加载 if (parent != null) { c = parent.loadClass(name, false); } else { // 如果自定义的类加载器的parent为null,就调用findBootstrapClassOrNull方法查找类,就是BootStrap类加载器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 类未发现异常 } if (c == null) { // 如果仍然没有发现,就调用自己的findClass方法进行进行类加载 long t1 = System.nanoTime(); 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; } }
-
defineClass,将class文件的字节数组编译成一个class对象,不能重写,内部实现为C/C++代码
图6.png
6). 自定义ClassLoader
- 定义一个被加载的类
/** * 加载类 * @author mazaiting */ public class ClassLoaderAttachment extends Date{ private static final long serialVersionUID = -1892974244318329380L; @Override public String toString() { return "Hello ClassLoader"; } }
- 自定义ClassLoader
/** * 自定义类加载器 * 步骤: * 1. 继承ClasssLoader * 2. 重写findClass(String name) 方法 * @author mazaiting */ public class MyClassLoader extends ClassLoader{ /**需要加载类.class文件的目录*/ private String classDir; /**无参构造*/ public MyClassLoader() {} public MyClassLoader(String classDir) { this.classDir = classDir; } // 测试,先将ClassLoaderAttachment.class文件加密到工程的class_temp目录下 public static void main(String[] args) throws Exception { // 配置运行参数 // ClassLoaderAttachment.class原路径--E:\test\java_advanced\ClassLoader\bin\com\mazaiting\ClassLoaderAttachment.class String srcPath = args[0]; // ClassLoaderAttachment.class输出路径--E:\test\java_advanced\ClassLoader\class_temp\ String desPath = args[1]; // 获取文件名 String desFileName = srcPath.substring(srcPath.lastIndexOf("\\") +1); // 配置目标文件路径 String desPathFile = desPath + "/" + desFileName; FileInputStream fis = new FileInputStream(srcPath); FileOutputStream fos = new FileOutputStream(desPathFile); // 将class进行加密 encodeAndDecode(fis, fos); fis.close(); fos.close(); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // class文件的路径 String classPathFile = classDir + "/" + name + ".class"; try { // 将class文件进行解密 FileInputStream fis = new FileInputStream(classPathFile); ByteArrayOutputStream baos = new ByteArrayOutputStream(); encodeAndDecode(fis, baos); byte[] classByte = baos.toByteArray(); // 将字节流变成一个class return defineClass(null, classByte, 0, classByte.length); } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } /** * 加密解密 * @param is 输入流 * @param os 输出流 * @throws IOException */ private static void encodeAndDecode(InputStream is, OutputStream os) throws IOException { int len; while ((len = is.read()) != -1) { len ^= 0xFF; os.write(len); } } }
- 运行:在MyClassLoader的main方法右键 -> Run As -> Run Configurations...,进行如下配置(配置前请确保ClassLoader工程目录下存在class_temp文件夹,其中第一个参数是ClassLoaderAttachment.class文件的源路径,第二个参数是加密后存放的目录),点击Run运行,此时在
E:\test\java_advanced\ClassLoader\class_temp
将会生成加密后的ClassLoaderAttachment.class
文件
图7.png - 使用,在
ClassLoaderTest
的main函数中应用
public class ClassLoaderTest { public static void main(String[] args) { try { // 加载类 Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment"); // 创建实例 Date date = (Date) classDate.newInstance(); // 输出ClassLoaderAttachment类的加载器名称 System.out.println("ClassLoader: " + date.getClass().getClassLoader().getClass().getName()); // 调用方法 System.out.println(date.toString()); } catch (Exception e) { e.printStackTrace(); } } }
打印结果:
- 如果loadClass方法的参数为类的全路径名
// 加载类 Class classDate = new MyClassLoader("class_temp").loadClass("com.mazaiting.ClassLoaderAttachment");
- 将
\class_temp\
路径下的ClassLoaderAttachment.class复制到\bin\com\mazaiting\
路径下,然后运行
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in class file com/mazaiting/ClassLoaderAttachment
不合适的魔数错误(class文件的前六个字节是个魔数用来标识class文件的),因为加密后的数据AppClasssLoader无法解析。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
【干货】深度学习中的线性代数—简明教程
Linear Algebra for Deep Learning 深度学习(Deep Learning)是机器学习的一个子领域,涉及的算法模仿大脑的功能和结构,称为人工神经网络。 线性代数(linear algebra)是有关连续值的数学。许多计算机科学家在此方面经验不足(传统上计算机科学更偏重离散数学)。想要理解和使用许多机器学习算法,特别是深度学习算法,对线性代数的良好理解是不可或缺的。 为什么要学习数学? 线性代数、概率论和微积分是用来表达机器学习的“语言”。学习这些内容将有助于深入理解底层的算法机制,也有助于开发新的算法。 当被限制在足够小的尺度上,深度学习背后的一切都是数学。所以在开始深度学习之前,了解基本的线性代数是非常重要的。 深度学习背后的核心数据结构是标量(Scalar)、向量(Vector)、矩阵(Matrix)、张量(Tensor)。在编程中,让我们使用这些数据结构求解基本的线性代数问题。 标量(Scalars) 标量是一个数字,或者说,是一个0阶张量。符号表示是一个属于实数集的标量。 深度学习中有着不同的数字集合。表示正整数集。表示整数集,包括正整数,负整数和零...
- 下一篇
App爬虫神器mitmproxy和mitmdump的使用
mitmproxy是一个支持HTTP和HTTPS的抓包程序,有类似Fiddler、Charles的功能,只不过它是一个控制台的形式操作。 mitmproxy还有两个关联组件。一个是mitmdump,它是mitmproxy的命令行接口,利用它我们可以对接Python脚本,用Python实现监听后的处理。另一个是mitmweb,它是一个Web程序,通过它我们可以清楚观察mitmproxy捕获的请求。 下面我们来了解它们的用法。 一、准备工作 请确保已经正确安装好了mitmproxy,并且手机和PC处于同一个局域网下,同时配置好了mitmproxy的CA证书。 二、mitmproxy的功能 mitmproxy有如下几项功能。 拦截HTTP和HTTPS请求和响应。 保存HTTP会话并进行分析。 模拟客户端发起请求,模拟服务端返回响应。 利用反向代理将流量转发
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Red5直播服务器,属于Java语言的直播服务器
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS8编译安装MySQL8.0.19
- CentOS关闭SELinux安全模块
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Windows10,CentOS7,CentOS8安装Nodejs环境