这篇文章介绍java的类加载机制。

Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言。Java程序在运行前需要先编译成class文件,Java类初始化的时候会调用java.lang.ClassLoader加载类字节码,ClassLoader会调用JVM的native方法(defineClass0/1/2)来定义一个java.lang.Class实例。

ClassLoader加载器

一切的Java类都必须经过JVM加载后才能运行,而ClassLoader的主要作用就是Java类文件的加载。



在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)、Extension ClassLoader(扩展类加载器)、App ClassLoader(系统类加载器),AppClassLoader是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader加载类,ClassLoader.getSystemClassLoader()返回的系统类加载器也是AppClassLoader。

ClassLoader类有如下核心方法:

  1. loadClass(加载指定的Java类)
  2. findClass(查找指定的Java类)
  3. findLoadedClass(查找JVM已经加载过的类)
  4. defineClass(定义一个Java类)
  5. resolveClass(链接指定的Java类)

Java类动态加载方式

Java类加载方式分为显式和隐式,显式即我们通常使用Java反射或者ClassLoader来动态加载一个类对象,而隐式指的是类名.方法名()或new类实例。显式类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。

// 反射加载TestHelloWorld示例
Class.forName("com.test.classloader.TestHelloWorld"); // ClassLoader加载TestHelloWorld示例
this.getClass().getClassLoader().loadClass("com.test.classloader.TestHelloWorld"); /*
Class.forName("类名")默认会初始化被加载类的静态属性和方法,
如果不希望初始化类可以使用Class.forName("类名", 是否初始化类, 类加载器),而ClassLoader.loadClass默认不会初始化类方法。
*/
//隐式
TestHelloWorld.get() Class test = new TestHelloWorld()

其他加载方式

URLClassLoader 任意类加载:file/http/jar

ClassLoader.defineClass 字节码加载任意类

Unsafe.defineClass 字节码加载

ClassLoader类加载流程

ClassLoader加载com.test.classloader.TestHelloWorld类重要流程如下:

1.ClassLoader会调用public Class<?> loadClass(String name)方法加载com.test.classloader.TestHelloWorld类。
2.调用findLoadedClass方法检查TestHelloWorld类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
3.如果创建当前ClassLoader时传入了父类加载器(new ClassLoader(父类加载器))就使用父类加载器加载TestHelloWorld类,否则使用JVM的Bootstrap ClassLoader加载。
4.如果上一步无法加载TestHelloWorld类,那么调用自身的findClass方法尝试加载TestHelloWorld类。
5.如果当前的ClassLoader没有重写了findClass方法,那么直接返回类加载失败异常。如果当前类重写了findClass方法并通过传入的com.test.classloader.TestHelloWorld类名找到了对应的类字节码,那么应该调用defineClass方法去JVM中注册该类。
6.如果调用loadClass的时候传入的resolve参数为true,那么还需要调用resolveClass方法链接类,默认为false。
7.返回一个被JVM加载后的java.lang.Class类对象。

自定义ClassLoader

java.lang.ClassLoader是所有的类加载器的父类,java.lang.ClassLoader有非常多的子类加载器,比如我们用于加载jar包的java.net.URLClassLoader其本身通过继承java.lang.ClassLoader类,重写了findClass方法从而实现了加载目录class文件甚至是远程资源文件。



但是如果com.test.classloader.TestHelloWorld根本就不存在于我们的classpath,那么我们可以使用自定义类加载器重写findClass方法,然后在调用defineClass方法的时候传入TestHelloWorld类的字节码的方式来向JVM中定义一个TestHelloWorld类,最后通过反射机制就可以调用TestHelloWorld类的hello方法了。

package com.test.classloader;

import java.lang.reflect.Method;

public class TestClassLoader extends ClassLoader {

    // TestHelloWorld类名
private static String testClassName = "com.test.classloader.TestHelloWorld"; // TestHelloWorld类字节码
private static byte[] testClassBytes = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101,
1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111,
114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115,
116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108,
97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0,
1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1,
0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11,
0, 0, 0, 2, 0, 12
}; @Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 只处理TestHelloWorld类
if (name.equals(testClassName)) {
// 调用JVM的native方法定义TestHelloWorld类
return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
} return super.findClass(name);
} public static void main(String[] args) {
// 创建自定义的类加载器
TestClassLoader loader = new TestClassLoader(); try {
// 使用自定义的类加载器加载TestHelloWorld类
Class testClass = loader.loadClass(testClassName); // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld();
Object testInstance = testClass.newInstance(); // 反射获取hello方法
Method method = testInstance.getClass().getMethod("hello"); // 反射调用hello方法,等价于 String str = t.hello();
String str = (String) method.invoke(testInstance); System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
} }

双亲委派

当一个类加载器收到类加载请求的时候,它首先不会自己去加载这个类的信息,而是把该请求转发给父类加载器,依次向上。所以所有的类加载请求都会被传递到父类加载器中,只有当父类加载器中无法加载到所需的类,子类加载器才会自己尝试去加载该类。当当前类加载器和所有父类加载器都无法加载该类时,抛出ClassNotFindException异常。

这么做的目的是:

1.防止重复加载同一个.class
2.保证核心.class不被篡改,提高系统安全性

类的卸载

1、 有JVM自带的三种类加载器Bootstrap ClassLoader(引导类加载器)、Extension ClassLoader(扩展类加载器)、App ClassLoader(系统类加载器)加载的类始终不会卸载。因为JVM始终引用这些类加载器,这些类加载器使用引用他们所加载的类,因此这些Class类对象始终是可到达的。

2、由用户自定义类加载器加载的类,是可以被卸载的。

JVM中的Class只有满足以下三个条件,才能被GC回收机制回收,也就是该Class被卸载(unload):
1.该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例
2.加载该类的ClassLoader已经被GC
3.该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法

javasec(三)类加载机制的更多相关文章

  1. 深入理解java虚拟机(三)-----类加载机制

    类加载机制jvm把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被jvm直接使用的java类型.在java中,类型的加载.连接和初始化都是在程序运行期间完成的 ...

  2. jvm虚拟机笔记<三> 类文件结构与类加载机制

    java虚拟机具有语言无关系,它只和“class文件“这种特定的二进制文件格式绑定. 不同语言的编译器将对应的程序编译成字节码文件(*.class),送给jvm执行. class文件本质上就是一张表, ...

  3. 深入理解Java虚拟机学习笔记(三)-----类文件结构/虚拟机类加载机制

    第6章 类文件结构 1. 无关性 各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码(即扩展名为 .class 的文件) 是构成平台无关性的基石. 字节码(即扩展名为 .class 的文 ...

  4. JVM初探(三):类加载机制

    一.概述 我们知道java代码会被编译为.class文件,这里class文件中的类信息最终还是需要jvm加载以后才能使用. 事实上,虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转 ...

  5. JVM学习第三天(JVM的执行子系统)之类加载机制补充

    昨晚没看完,今天继续 系统的类加载器 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间.这句话可以表达得更通俗一些: ...

  6. JVM 第三篇:Java 类加载机制

    本文内容过于硬核,建议有 Java 相关经验人士阅读. 1. 什么是类的加载? 类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 ...

  7. JVM(三)-java虚拟机类加载机制

    概述: 上一篇文章,介绍了java虚拟机的运行时区域,Java虚拟机根据不同的分工,把内存划分为各个不同的区域.在java程序中,最小的运行单元一般都是创建一个对象,然后调用对象的某个 方法.通过上一 ...

  8. 【深入理解JAVA虚拟机】第三部分.虚拟机执行子系统.2.虚拟机类加载机制

    1.概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验. 转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 2.类加载顺序 类从被加载到虚拟 ...

  9. jvm--2.类加载机制

    3.JVM类加载机制 (1)类加载机制 虚拟机把描述类的数据从Class文件,用ClassLoader ,加载到内存,并对数据进行校验.转换解析和初始化,最终形成虚拟机直接使用的java类型, 这就是 ...

  10. 【JVM】JVM系列之类加载机制(四)

    一.前言 前面分析了class文件具体含义,接着需要将class文件加载到虚拟机中,这个过程是怎样的呢,下面,我们来仔细分析. 二.什么是类加载机制 把class文件加载到内存,并对数据进行校验.转换 ...

随机推荐

  1. 解决git仓库项目 添加到github非空仓库冲突问题 error: failed to push some refs to 'https://github.com/Qtoken/......'

    error: failed to push some refs to 'https://github.com/Qtoken/......' 1. 问题描述:执行命令:git push origin m ...

  2. 使用netty 实现本地代理程序

    本地代理程序1:将远程的服务设置为本地端口访问我的台式PC安装了vm,因为都是机器私有IP,但我的另外的PC电脑也需要访问方便测试,需要要把VM的端口设置在台式本机对外,这样我台式的端口对外在局域网都 ...

  3. printf( )和scanf( )

    printf()的转换说明 转换说明 输出 %a,%A 浮点数,十六进制数和p记数法 %c 单个字符 %d.%i 有符号的十进制整数 %e,%E 浮点数,e记数法 %f 浮点数,十进制计数法 %g/% ...

  4. ASP.NET在Repeater中使用Button控件报错

    普通Button在这里会报错,小编找了一天也没有解决这个问题, 这里可以换做LinkButton或者ImageButton替换普通的Button

  5. oracle数据库常用操作

    1,调整显示格式 col username for a20 col DEFAULT_TABLESPACE for a30 2,查看表空间 select username,default_tablesp ...

  6. xlsx合并单元格简单介绍

    在使用xlsx导出excel表格的时候,有时候我们需要将某些表格进行合并,该如何做呢,代码如下: import XLSX from 'xlsx'; // ... // xlsxData 是 Excel ...

  7. 内网jenkins跨版本升级

    概要: 原来使用的jenkins版本为1.6,现在需要升级为最新版2.3.6 由于在内网,不能使用jenkins自带的在线升级工具 升级思路: 由于版本跨度太大,直接copy jenkins目录,启动 ...

  8. goujian

    第二章的内容先对第一张来说更偏向于技术性,在团队合作中,如何保证自己所负责模块的质量的稳定,这就对自身的技术和一些良好的代码书写习惯有一定的要求.这里除了之前接触的代码的整齐(段落划分),变量值和文件 ...

  9. 获取了文心一言的内测及与其ChatGPT、GPT-4 对比结果

    百度在3月16日召开了关于文心一言(知识增强大语言模型)的发布会,但是会上并没现场展示demo.如果要测试的文心一言 也要获取邀请码,才能进行测试的. 我这边通过预约得到了邀请码,大概是在3月17日晚 ...

  10. Rainbond PipeLine插件部署与springboot应用部署实践

    前言:上一篇介绍额rainbond单机部署+单个节点的k8s环境搭建,本篇介绍rainbond5.12新增的pipeline插件的使用 1.Pipeline插件的安装 安装gitlab与gitlab- ...