前言

Java程序运行图:

上一篇玩命学JVM(一)—认识JVM和字节码文件我们简单认识了 JVM 和字节码文件。那JVM是如何使用字节码文件的呢?从上图清晰地可以看到,JVM 通过类加载器完成了这一过程。

以下是类加载机制的知识框架:

接下来我们对思维导图中重难点部分做补充。

1. 是什么?

类的加载就是将 .class 文件的二进制数据读入到内存中,将其放在 JVM 的运行时数据区的方法区内。然后在堆区内创建一个 java.lang.Class 对象,用于封装类在方法区内的数据结构。

5. 双亲委派模型

双亲委派模型图如下:

对于“双亲委派模型”,首先需要纠正一点,“双亲”并不是说它有“两个亲”。实际上行“双亲委派模型”和“双”毫无关系,只和“亲”有关系。

其实“双亲”是翻译的一个错误,原文出处是“parent”,被翻译成了“双亲”,在计算机领域更常见的说法是“父节点”。所以如果将“双亲委派模型”改为“父委派模型”,应该更好理解。

结合实际的类加载器来说,就是:

  1. 每个类加载器都会向上找自己父类加载器尝试完成类加载;
  2. 父加载器加载失败会向下找加载器尝试加载。

    如图:

接下来我们从源码上来分析下 双亲委派模型

Bootstrap ClassLoader外,其它的类加载器都是ClassLoader的子类。加载类的方法为loadClass,查看源码可发现,loadClassClassLoader中有具体的实现,且在各个子类中都没有被覆盖。

先介绍三个重要的函数,对后续的源码阅读有帮助:

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只在父类中实现了这一点。

自定义类加载器

自定义类加载主要有两种方式:

  1. 遵守双亲委派模型:继承ClassLoader,重写findClass()方法。

  2. 破坏双亲委派模型:继承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(二)—类加载机制的更多相关文章

  1. 一文教你读懂JVM的类加载机制

    Java运行程序又被称为WORA(Write Once Run Anywhere,在任何地方运行只需写入一次),意味着我们程序员小哥哥可以在任何一个系统上开发Java程序,但是却可以在所有系统上畅通运 ...

  2. JVM内存结构 JVM的类加载机制

    JVM内存结构: 1.java虚拟机栈:存放的是对象的引用(指针)和局部变量 2.程序计数器:每个线程都有一个程序计数器,跟踪代码运行到哪个位置了 3.堆:对象.数组 4.方法区:字节流(字节码文件) ...

  3. JVM之类加载机制

    JVM之类加载机制 JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 类加载五部分 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这 ...

  4. JVM的类加载机制全面解析

    什么是类加载机制 JVM把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被JVM直接使用的Java类型,这就是JVM的类加载机制. 如果你对Class文件的结 ...

  5. 大白话谈JVM的类加载机制

    前言 我们很多小伙伴平时都是做JAVA开发的,那么作为一名合格的工程师,你是否有仔细的思考过JVM的运行原理呢. 如果懂得了JVM的运行原理和内存模型,像是一些JVM调优.垃圾回收机制等等的问题我们才 ...

  6. JVM活学活用——类加载机制

    类的实例化过程 有父类的情况 1. 加载父类静态    1.1 为静态属性分配存储空间并赋初始值     1.2 执行静态初始化块和静态初始化语句(从上至下) 2. 加载子类静态    2.1 为静态 ...

  7. JVM学习——类加载机制(学习过程)

    JVM--类加载机制 2020年02月07日14:49:19-开始学习JVM(Class Loader) 类加载机制 类加载器深入解析与阶段分解 在Java代码中,类型的加载.连接与初始化过程中都是在 ...

  8. JVM虚拟机—JVM的类加载机制

    1 什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...

  9. JVM的类加载机制

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 类加载的过程: 包括加载.链接(含验证.准备 ...

随机推荐

  1. Min Cost Climbing Stairs [746]

    Min Cost Climbing Stairs [746] 题目描述 简单来说就是:要跳过当前楼梯需要花费当前楼梯所代表的价值cost[i], 花费cost[i]之后,可以选择跳一阶或者两阶楼梯,以 ...

  2. 安装oracleXE快捷版(一)

    yum找不到包,参考了一些文章,用iso上的包安装了.在文章后面贴有我实际的操作(黑体)和日志. 更换yum源https://www.cnblogs.com/zrxuexi/p/11587173.ht ...

  3. composer源码简单分析(一)

    composer分析(一) 本文内容 基于PSR-4规范的自动加载 请结合文档和下面的代码注释 spl_autoload_register php闭包Closure简单用法(大体使用情景: 生成回调提 ...

  4. 领导给了一堆无序杂乱的数据,我写了个Python自动化脚本

    这个问题算是群友答疑.如果说同事或者老板给你一堆这样的数据,你估计会抓狂,该怎么处理呢? 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手. ...

  5. OC基础--数据类型与表达式

    前言 做iOS开发有3年了,从当初的小白到现在,断断续续看过很多资料,之前也写过一些博文来记录,但是感觉知识点都比较凌乱.所以最近准备抽时间把iOS开发的相关知识进行一个梳理,主要分为OC基础.UI控 ...

  6. 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\). 解题思路 这题其实第一眼 ...

  7. 按照BNF语法重新写就的JsonAnalyzer2

    本例源码:https://files.cnblogs.com/files/heyang78/JsonAnalyzer2-20200525-01.rar 自从按BNF重新书写了算术表达式解析(https ...

  8. Netty进阶和实战

    实现UDP单播和广播 UDP 这样的无连接协议中,并没有持久化连接这样的概念,并且每个消息(一个UDP 数据报)都是一个单独的传输单元.此外,UDP 也没有TCP 的纠错机制. 通过类比,TCP 连接 ...

  9. leetcode刷题-90子集 II

    题目 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: [1,2,2]输出:[ [2], [1], [1,2,2], [ ...

  10. Java类加载机制()

    Java类加载机制(转载) 概述 在开始正文之前,我们先看两张图 .Java平台的理解?Java最显著的特性?Java是解释执行? 先看一下java程序的执行流程图 再看一下jvm的大致物理结构图 本 ...