玩命学JVM(二)—类加载机制
前言
Java程序运行图:
上一篇玩命学JVM(一)—认识JVM和字节码文件我们简单认识了 JVM 和字节码文件。那JVM是如何使用字节码文件的呢?从上图清晰地可以看到,JVM 通过类加载器完成了这一过程。
以下是类加载机制的知识框架:
接下来我们对思维导图中重难点部分做补充。
1. 是什么?
类的加载就是将 .class 文件的二进制数据读入到内存中,将其放在 JVM 的运行时数据区的方法区内。然后在堆区内创建一个 java.lang.Class 对象,用于封装类在方法区内的数据结构。
5. 双亲委派模型
双亲委派模型图如下:
对于“双亲委派模型”,首先需要纠正一点,“双亲”并不是说它有“两个亲”。实际上行“双亲委派模型”和“双”毫无关系,只和“亲”有关系。
其实“双亲”是翻译的一个错误,原文出处是“parent”,被翻译成了“双亲”,在计算机领域更常见的说法是“父节点”。所以如果将“双亲委派模型”改为“父委派模型”,应该更好理解。
结合实际的类加载器来说,就是:
- 每个类加载器都会向上找自己父类加载器尝试完成类加载;
- 父加载器加载失败会向下找加载器尝试加载。
如图:
接下来我们从源码上来分析下 双亲委派模型
除Bootstrap ClassLoader
外,其它的类加载器都是ClassLoader
的子类。加载类的方法为loadClass
,查看源码可发现,loadClass
在ClassLoader
中有具体的实现,且在各个子类中都没有被覆盖。
先介绍三个重要的函数,对后续的源码阅读有帮助:
loadClass
:调用父类加载器的loadClass,加载失败则调用自己的findClass方法。
findClass
:根据名称读取文件存入字节数组。
defineClass
:把一个字节数组转为Class对象。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 在JVM中查看类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 调用父类加载器的 loadClass方法,parent是该类加载器的父类,parent的值可能为 Application ClassLoader、Extension ClassLoader,当想要继续往上找 Extension ClassLoader时,由于Bootstrap ClassLoader是C/C++实现的,所以在java中是Null
c = parent.loadClass(name, false);
} else {
// 寻找 Bootstrap ClassLoader 加载
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();
// 父加载器开始尝试加载.class文件,加载成功就返回一个java.lang.Class,加载不成功就抛出一个ClassNotFoundException,给子加载器去加载
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) {
// 如果要解析这个.class文件的话,就解析一下,解析的作用主要就是将符号引用替换为直接引用的过程
resolveClass(c);
}
return c;
}
}
所谓的双亲委派模型,就是利用了loadClass
只在父类中实现了这一点。
自定义类加载器
自定义类加载主要有两种方式:
遵守双亲委派模型:继承ClassLoader,重写findClass()方法。
破坏双亲委派模型:继承ClassLoader,重写loadClass()方法。 通常我们推荐采用第一种方法自定义类加载器,最大程度上的遵守双亲委派模型。
我们看一下实现步骤
(1)创建一个类继承ClassLoader抽象类
(2)重写findClass()方法
(3)在findClass()方法中调用defineClass()
第一步,自定义一个实体类Person.java,我把它编译后的Person.class放在D盘根目录下:
package com.xrq.classloader;
public class Person
{
private String name;
public Person()
{
}
public Person(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String toString()
{
return "I am a person, my name is " + name;
}
}
第二步,自定义一个类加载器,里面主要是一些IO和NIO的内容,另外注意一下 defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class----只要二进制字节流的内容符合Class文件规 范。我们自定义的MyClassLoader继承自java.lang.ClassLoader,就像上面说的,只实现findClass方法:
public class MyClassLoader extends ClassLoader
{
public MyClassLoader()
{
}
public MyClassLoader(ClassLoader parent)
{
super(parent);
}
protected Class<?> findClass(String name) throws ClassNotFoundException
{
File file = getClassFile(name);
try
{
byte[] bytes = getClassBytes(file);
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
}
catch (Exception e)
{
e.printStackTrace();
}
return super.findClass(name);
}
private File getClassFile(String name)
{
File file = new File("D:/Person.class");
return file;
}
private byte[] getClassBytes(File file) throws Exception
{
// 这里要读入.class的字节,因此要使用字节流
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel wbc = Channels.newChannel(baos);
ByteBuffer by = ByteBuffer.allocate(1024);
while (true)
{
int i = fc.read(by);
if (i == 0 || i == -1)
break;
by.flip();
wbc.write(by);
by.clear();
}
fis.close();
return baos.toByteArray();
}
}
第三步,Class.forName有一个三个参数的重载方法,可以指定类加载器,平时我们使用的Class.forName("XX.XX.XXX")都是使用的系统类加载器Application ClassLoader。写一个测试类:
public class TestMyClassLoader
{
public static void main(String[] args) throws Exception
{
MyClassLoader mcl = new MyClassLoader();
Class<?> c1 = Class.forName("com.xrq.classloader.Person", true, mcl);
Object obj = c1.newInstance();
System.out.println(obj);
System.out.println(obj.getClass().getClassLoader());
}
}
运行结果:
I am a person, my name is null
com.xrq.classloader.MyClassLoader@5d888759
参考文献
https://baijiahao.baidu.com/s?id=1636309817155065432&wfr=spider&for=pc
https://blog.csdn.net/qq_44836294/article/details/105439753
玩命学JVM(二)—类加载机制的更多相关文章
- 一文教你读懂JVM的类加载机制
Java运行程序又被称为WORA(Write Once Run Anywhere,在任何地方运行只需写入一次),意味着我们程序员小哥哥可以在任何一个系统上开发Java程序,但是却可以在所有系统上畅通运 ...
- JVM内存结构 JVM的类加载机制
JVM内存结构: 1.java虚拟机栈:存放的是对象的引用(指针)和局部变量 2.程序计数器:每个线程都有一个程序计数器,跟踪代码运行到哪个位置了 3.堆:对象.数组 4.方法区:字节流(字节码文件) ...
- JVM之类加载机制
JVM之类加载机制 JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 类加载五部分 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这 ...
- JVM的类加载机制全面解析
什么是类加载机制 JVM把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被JVM直接使用的Java类型,这就是JVM的类加载机制. 如果你对Class文件的结 ...
- 大白话谈JVM的类加载机制
前言 我们很多小伙伴平时都是做JAVA开发的,那么作为一名合格的工程师,你是否有仔细的思考过JVM的运行原理呢. 如果懂得了JVM的运行原理和内存模型,像是一些JVM调优.垃圾回收机制等等的问题我们才 ...
- JVM活学活用——类加载机制
类的实例化过程 有父类的情况 1. 加载父类静态 1.1 为静态属性分配存储空间并赋初始值 1.2 执行静态初始化块和静态初始化语句(从上至下) 2. 加载子类静态 2.1 为静态 ...
- JVM学习——类加载机制(学习过程)
JVM--类加载机制 2020年02月07日14:49:19-开始学习JVM(Class Loader) 类加载机制 类加载器深入解析与阶段分解 在Java代码中,类型的加载.连接与初始化过程中都是在 ...
- JVM虚拟机—JVM的类加载机制
1 什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...
- JVM的类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 类加载的过程: 包括加载.链接(含验证.准备 ...
随机推荐
- vagrant安装的坑
关于VAGRANT安装过程的坑 标签: 虚拟化 闲来无聊 试试vagrant虚拟化技术 安装过程中坑太多了 所以记录下方便以后 注意: 执行 vagrant up 命令报错 如下 PS F ...
- 程序员软件开发最好的IDE集成工具eclipse各个版本的详细介绍。详细介绍,送给初学者的朋友
对于刚接触软件开发的初学者,在下载eclipse时,对官网上面提供的各种版本的选择犹豫不决.下面将对常用的几个版本进行介绍. Eclipse版本 Eclipse Standard 该版本是eclips ...
- Unity 深度冲突的解决方法
Dillon|2014-02-12 10:00|5899次浏览|Unity(280)0 3d游戏中当2个片元距离近裁减平面 w 落在同一个区间的时候,他们的深度是相等的. 最终你所看到的结果,就是下 ...
- Codeforces 1337D Xenia and Colorful Gems
题意 给你3个数组\(a, b\)和\(c\),最小化\((x-y)^2+(y-z)^2+(z-x)^2\),其中\(x \in a, y \in b, z \in c\). 解题思路 这题其实第一眼 ...
- JDK15就要来了,你却还不知道JDK8的新特性!
微信搜「烟雨星空」,白嫖更多好文. 现在 Oracle 官方每隔半年就会出一个 JDK 新版本.按时间来算的话,这个月就要出 JDK15 了.然而,大部分公司还是在使用 JDK7 和 8 . 之前去我 ...
- Hive 高阶应用开发示例(一)
Hive的一些常用的高阶开发 内容 1.开窗函数 2.行转列,列转行,多行转一行,一行转多行 3.分组: 增强型group 4.排序 5.关联 本次的内容: 内容1 和内容2,采用 ...
- C++——大小写转换
代码如下: #include <iostream> using namespace std; int main() { char ch; cin>>ch; if(ch>' ...
- 目标检测中的IOU和CIOU原理讲解以及应用(附测试代码)
上期讲解了目标检测中的三种数据增强的方法,这期我们讲讲目标检测中用来评估对象检测算法的IOU和CIOU的原理应用以及代码实现. 交并比IOU(Intersection over union) 在目标检 ...
- 项目实战:Qt+Ffmpeg+OpenCV相机程序(打开摄像头、支持多种摄像头、分辨率调整、翻转、旋转、亮度调整、拍照、录像、回放图片、回放录像)
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- Java常用工具类整理
字符数组转String package com.sunsheen.hcc.fabric.utils; /** * 字符数组工具 * @author WangSong * */ public class ...