浅谈类加载
类加载包括加载,链接,初始化三个过程。加载过程中通过全限定类名将二进制数据加载到内存中;链接阶段包括验证,准备以及解析,其中验证指的是对class二进制文件格式的校验,准备阶段为类中的静态变量分配内存,解析阶段为这些静态变量赋默认值;初始化阶段才为静态变量赋初值,以及执行静态代码块。
加载
1)通过全限定类名获取该类的二进制字节流
2)将字节流(.class二进制文件)表示的静态存储结构转化为方法区的运行时数据机构
3)内存中生成表示该类的Class对象。
类加载器
主要完成加载过程中的第一步,通过该类的全限定类名来获取描述该类的二进制字节流。
jdk1.8之后类加载器有如下四种,启动类加载器(Bootstrap Class Loader),扩展类加载器(Extension Class Loader),应用程序类加载器(Application Class Loader)以及自定义类加载器(User Class Loader)。
其中bootstrap Class Loader 负责加载存放在lib目录下能被JVM识别的类。
Extension Class Loader负责加载libext目录中的类。
Application Class Loader负责加载用户类路径CLASSPATH上的所有类。
双亲委派机制
类加载器的双亲委派模型如上图所示。需要注意的是上述箭头并不是继承关系,其关系为各个孩子中保存其父亲的引用,为如下代码中的parent。其被final修饰也就意味着父子关系是不能改变的。
public abstract class ClassLoader {
private final ClassLoader parent;
}
类加载的过程是采用双亲委派模型,其工作过程为:当一个类需要被加载时其会先在自己的类加载器的缓存中查找,该类是否之前已经被加载了,若加载了返回即可,若未被加载则不会自己直接加载,而是询问其的父亲是否已经加载,如此直到启动类加载器,若启动类加载器缓存中还是没有,启动类加载器会尝试着加载,若该类在lib目录下,则加载即可,如若不存在,则让其的孩子加载器进行加载,如此直到其自己的加载类,此时才可以使用自己的类加载器加载。其源码如下:
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) {
// 委派给其parent进行加载
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) {
long t1 = System.nanoTime();
// 父类加载器无法加载时,再调用本身的findClass进行加载
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
// 使用模板设计模式
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
为何要使用双亲委派机制?
使用双亲委派机制主要解决安全问题。反之,假设对于每个类都使用自己的类加载器加载的话,当某个用户编写一个java.lang.Object类,覆盖了系统中的Object类,java体系中最基础的行为都就不能保证了。使用双亲委派就不会有这问题,其会首先让自底而上查找,查找启动类加载器发现已经加载过了,因此不会加载该用户写的这个Object。
自定义类加载器
只需继承ClassLoader抽象类,并重写其模板方法findClass()即可。
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File file = new File("....", name.replaceAll(".", "/").concat(".class"));
try {
// 将class文件写入字节数组
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while((b = fis.read()) != 0) {
baos.write(b);
}
byte[] bytes = baos.toByteArray();
baos.close();
fis.close();
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
链接
验证:验证二进制class文件是否符合JVM规定
比较:静态变量赋默认值,例如int 0, long 0L boolean false, reference null等。
解析:将常量池中的符号引用替换为直接引用。
初始化
调用类初始化代码< clinit > ,给静态成员变量赋初始值。
- Mercari Price 比赛分享 —— 语言不仅是算法和公式而已
- Github 项目推荐 | GAN 的 Keras 实现案例集合 —— Keras-GAN
- Github 项目推荐 | 微软开源 MMdnn,模型可在多框架间转换
- 半自动化运维之动态添加数据文件(一) (r5笔记第55天)
- 半自动化运维之动态添加数据文件(二) (r5笔记第56天)
- 11g Active DataGuard初探(r5笔记第54天)
- Github 项目推荐 | 用于构建端对端对话系统和训练聊天机器人的开源库 —— DeepPavlov
- 我身边的一些数据库事故 (r5笔记第52天)
- 一个清理脚本的改进思路(r5笔记第51天)
- 【专业技术】Python爬虫:抓取手机APP的传输数据
- 海量数据迁移之传输表空间(一) (r5笔记第71天)
- 一条sql语句的改进探索(r5笔记第70天)
- 【专业技术】Node.js 究竟是什么?
- Github 项目推荐 | 用 Pytorch 实现的 WaveNet-Vocoder
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- CentOS使用本地yum源搭建LAMP环境图文教程
- 清除CentOS 6或CentOS 7上的磁盘空间的方法
- leetcode栈之二叉树的前序遍历
- 解决Linux下Mysql5.7忘记密码问题
- CentOS8.0 安装配置ftp服务器的实现方法
- Linux实现自动登录的实例讲解
- Linux中date命令转换日期提示date: illegal time format问题解决
- leetcode队列之最近的请求次数
- 安防视频云服务EasyCVR集成海康SDK时语音对出现杂音问题,如何解决?
- arm linux利用alsa驱动并使用usb音频设备
- linux 磁盘转移空间的方法
- 详解git中配置的.gitignore不生效的解决办法
- Apache Thrift环境配置
- CentOS 7更新时出现:Multilib version problems问题的解决方法
- Linux模拟网络丢包与延迟的方法