文章目录

class装载验证流程

class装载验证流程

加载

链接(验证、准备、解析)

初始化

class装载验证流程 -加载

装载类的第一个阶段

取得类的二进制流

转为方法区数据结构

在Java堆中生成对应的java.lang.Class对象

class装载验证流程 -链接 验证

链接 -> 验证

目的:保证Class流的格式是正确的

文件格式的验证

是否以0xCAFEBABE开头

版本号是否合理

元数据验证(class文件简单语义的验证)

是否有父类(比如某个类继承了某个类,可是这个类根本就是不存在的。)

继承了final类?(继承了final的方法或者修改了final属性)

非抽象类实现了所有的抽象方法(非抽象类实现接口中所有的非抽象方法)

字节码验证 (很复杂)

运行检查

栈数据类型和操作码数据参数吻合(分配了两个字的空间,可是运行的时候可能不只是两个字、分配了两个局部变量,可是运行的时候发现很多的局部变量)

跳转指令指定到合理的位置(跳转至零跳转到字节码的一个偏移量上面,比如本来就五十个字节,结果跳转到第五十一个字节上去了。)

符号引用验证

常量池中描述类是否存在(比如一个类继承了某个类,可是这个接口或者类实际上是不存在。)

访问的方法或字段是否存在且有足够的权限(访问的方法或者字段的权限是否足够(public private等))

class装载验证流程 -链接 准备

分配内存,并为类设置初始值 (方法区中)

public static int v=1;

在准备阶段中,v会被设置为0

在初始化的中才会被设置为1

对于static final类型,在准备阶段就会被赋上正确的值

public static final int v=1;

class装载验证流程 -链接 解析

符号引用替换为直接引用

符号引用就是字符串,默认的超类就是java.lang.Object,符号引用就是在常亮池里面有个字符串,字符串的内容就是java.lang.Object,符号引用并不能被用,只是一种表示的方式,直接就是指针或者地址偏移量,因为最后一定是指向一个内存地址,替换为直接引用之后,class才能够用自己需要引用的内容。

符号引用:字符串引用对象不一定被加载

直接引用:指针或者地址偏移量引用对象一定在内存

class装载验证流程 – 初始化

执行类构造器

static变量 赋值语句

static{}语句

子类的调用前保证父类的被调用

是线程安全的(一个线程进去之后其他的等待)

小问题

Java.lang.NoSuchFieldError错误可能在什么阶段抛出?

什么是类装载器ClassLoader

ClassLoader是一个抽象类

ClassLoader的实例将读入Java字节码将类装载到JVM中

ClassLoader可以定制,满足不同的字节码流获取方式(网络中、文件中)

ClassLoader负责类装载过程中的加载阶段(连接和初始化阶段是和ClassLoader是没有关系的)

ClassLoader的重要方法

public Class<?> loadClass(String name) throws ClassNotFoundException

根据名字加载一个class,并返回这个class类的信息。

protected final Class<?> defineClass(byte[] b, int off, int len)

定义一个类,参数:byte数组、偏移量、长度,不公开调用,byte数组中是二进制的字节码,二进制的流信息,就是class文件里面的内容,把二进制文件的信息转化成class文件的内容。

protected Class<?> findClass(String name) throws ClassNotFoundException

loadClass回调该方法,即loadClass里面会调用findClass方法,去做类的查找。自定义ClassLoader的推荐做法

protected final Class<?> findLoadedClass(String name)

寻找已经加载的类,只有查找不到才会做加载,如果已经加载了不会做二次的加载。

JDK中ClassLoader默认设计模式

BootStrap ClassLoader (启动ClassLoader)

Extension ClassLoader (扩展ClassLoader)

App ClassLoader (应用ClassLoader/系统ClassLoader)

Custom ClassLoader(自定义ClassLoader)

每个ClassLoader都有一个Parent作为父亲

JDK中ClassLoader默认设计模式 – 协同工作



注意:向上查找,不是查找这个类存在不存在,而是查找类被加载了没有!!!

注意:向上查找,不是查找这个类存在不存在,而是查找类被加载了没有!!!

注意:向上查找,不是查找这个类存在不存在,而是查找类被加载了没有!!!

当找类的时候在当前的classloder找,即AppClassLoader,如果没有找到会将查找的请求给父类,ExtensionClassLoader,如果有则ExtensionClassLoader做加载,如果还没有,将查找的请求给BootsTrapClassLoader,有则加载,如果没有则说明这个类的整个ClassLoader的整个系列中都没有这个类,它就会尝试去加载。

加载的方法是从上往下的,并不是APPClassLoader找不到就让APPClassLoader做加载,先由BootsTrapClassLoader做加载,如果BootsTrapClassLoader加载成功了,下面的ClassLoader就不做事情,如果BootsTrapClassLoader没有加载成功,就让ExtensionClassLoader,做加载,如果ExtensionClassLoader没有加载成功,再让APPClassLoader加载。由此可见,如果一个class由BootsTrapClassLoader加载之后,再去询问,在ExtensionClassLoader中是没有的,因为不是第一个尝试加载的ClassLoader

  • BootsTrapClassLoader 中是$JAVA_HOME/jre/lib/rt.jar中的内容,通常是java中的系统核心类,同样可以再启动jar的时候通过-Xbootsclasspath,使得后面的class文件通过BootsTrapClassLoader加载
  • ExtensionClassLoader加载$JAVAHOME/lib/ext/*.jar中的class内容
  • APPClassLoader加载来自在命令java中的classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径,也就是我们经常用到的classpath路径

    classpath的默认路径是当前路径

JDK中ClassLoader默认设计模式 – 协同工作

protected synchronized Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
//查看是否加载了类var1,加载过的话返回class,确保类只加载一次。
        Class var3 = this.findLoadedClass(var1);
        //如果找不到
        if (var3 == null) {
            try {
            //请求父类做加载
                if (this.parent != null) {
                    var3 = this.parent.loadClass(var1, false);
                } else {
                    var3 = this.findBootstrapClassOrNull(var1);
                }
            } catch (ClassNotFoundException var5) {
                ;
            }

            if (var3 == null) {
                var3 = this.findClass(var1);
            }
}

举个栗子



描述:起初是左边的HelloLoader在如图的包下面,之后再在本机的clz目录下新建一个HelloLoader(红色部分)

  • 直接运行以上代码:

    输出:I am in apploader
  • 加上参数 -Xbootclasspath/a:D:/tmp/clz

    输出:I am in bootloader

    此时AppLoader中不会加载HelloLoader

    I am in apploader 在classpath中却没有加载

    说明类加载是从上往下的

解析:

注意:向上查找,不是查找这个类存在不存在,而是查找类被加载了没有!!!

注意:向上查找,不是查找这个类存在不存在,而是查找类被加载了没有!!!

注意:向上查找,不是查找这个类存在不存在,而是查找类被加载了没有!!!

classpath的默认路径是当前路径

  • 没有加入-Xbootclasspath/a:D:/tmp/clz命令的时候

    首次肯定都没有被加载,这个时候,依次通过BootsTrapClassLoader(rt.jar/- XbootClasspath,发现没有),ExtensionClassLoader (lib/ext,还是没有),最后加载APPCLassLoader(因为 classpath的默认路径是当前路径 ,所以能够加载到),最后输出了I am in apploader

  • 加入-Xbootclasspath/a:D:/tmp/clz参数

    查找同上,首次加载查找一圈都没有查找到被加载,然后从上往下加载,这个时候由于设置了BootsTrapClassLoader的参数,所以在BootsTrapClassLoader层面就已经能够被加载到了,下面也不会再被加载,所以输出I am in bootloader

强制在apploader中加载

public static void main(String args[]) throws Exception {
	ClassLoader cl=FindClassOrder2.class.getClassLoader();
	//得到geym.jvm.ch6.findorder.HelloLoader的字节码
	byte[] bHelloLoader=loadClassBytes("geym.jvm.ch6.findorder.HelloLoader");
	//为甚么通过反射得到这个函数?因为这个函数是protect的
	Method md_defineClass=ClassLoader.class.getDeclaredMethod("defineClass", byte[].class,int.class,int.class);
	//set为true能够使用
	//将此对象的 accessible 标志设置为指示的布尔值。
	//值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
	//值为 false 则指示反射的对象应该实施 Java 语言访问检查。
	md_defineClass.setAccessible(true);
	md_defineClass.invoke(cl, bHelloLoader,0,bHelloLoader.length);
	md_defineClass.setAccessible(false);

	HelloLoader loader = new HelloLoader();
	System.out.println(loader.getClass().getClassLoader());
	loader.print();
}

依然添加参数-Xbootclasspath/a:D:/tmp/clz

输出:I am in apploader

在查找类的时候,先在底层的Loader查找,是从下往上的。因为在APPLoader已经加载了,所以Apploader能找到,就不会去上层加载器加载

小问题

能否只用反射,仿照上面的写法,将类注入启动ClassLoader呢?

JDK中ClassLoader默认设计模式 – 问题



如上图,接口位于rt.jar,实现类位于APPClassLoader,如果想要实现这个类,必须要在BootsTrapClassLoader中知道下面即APPClassLoader中的内容,可是这种双亲委派的机制,自底向上检查是否被夹在,APPClassLoader可以知道BootsTrapClassLoader中的内容,加载是自顶向下,加载了BootsTrapClassLoader之后就不能再加载ExtensionClassLoader和APPClassLoader,也就不能知道APPClassLoader中的内容,所以永远无法知道。

JDK中ClassLoader默认设计模式 – 解决

  • Thread. setContextClassLoader()

    上下文加载器

    是一个角色

    解释:角色是什么意思?小明是班里的一个成员,他的职务是班长,小明是ClassLoader,班长就是角色。

    用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题

    基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例
tatic private Class getProviderClass(String className, ClassLoader cl,
        boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
{
    try {
        if (cl == null) {
            if (useBSClsLoader) {
                return Class.forName(className, true, FactoryFinder.class.getClassLoader());
            } else {
                cl = ss.getContextClassLoader();
                if (cl == null) {
                    throw new ClassNotFoundException();
                }
                else {
                    return cl.loadClass(className); //使用上下文ClassLoader
                }
            }
        }
        else {
            return cl.loadClass(className);
        }
    }
    catch (ClassNotFoundException e1) {
        if (doFallback) {
            // Use current class loader - should always be bootstrap CL
            return Class.forName(className, true, FactoryFinder.class.getClassLoader());
        }
…..

注意上面代码中的

return cl.loadClass(className);

中的cl就是ContextClassLoader

代码来自于rt.jar中的

javax.xml.parsers.FactoryFinder

展示如何在启动类加载器加载AppLoader的类

上下文ClassLoader可以突破双亲模式的局限性

双亲模式的破坏

双亲模式是默认的模式,但不是必须这么做

Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent

OSGi的ClassLoader形成网状结构,根据需要自由加载Class,因为他是热加载,一会加载了一会又不加载了,所以就是网状结构。

举个栗子

破坏双亲模式例子- 先从底层ClassLoader加载

OrderClassLoader的部分实现

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // First, check if the class has already been loaded
    //现在自己的层面查找,
    Class re=findClass(name);
    //找不到再去找父类
    if(re==null){
        System.out.println(“无法载入类:”+name+“ 需要请求父加载器");
        return super.loadClass(name,resolve);
    }
    return re;
}
protected Class<?> findClass(String className) throws ClassNotFoundException {
//在自己层面上找是否加载了类
Class clazz = this.findLoadedClass(className);
if (null == clazz) {
    try {
        String classFile = getClassFile(className);
        //没有的话就在自己的层面上加载某个文件
        FileInputStream fis = new FileInputStream(classFile);
        FileChannel fileC = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel outC = Channels.newChannel(baos);
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
         省略部分代码
        fis.close();
        byte[] bytes = baos.toByteArray();
//定义某个类
        clazz = defineClass(className, bytes, 0, bytes.length);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
return clazz;
}

所以达到了从自己开始加载的目的

OrderClassLoader myLoader=new OrderClassLoader("D:/tmp/clz/");
Class clz=myLoader.loadClass("geym.jvm.ch6.classloader.DemoA");
System.out.println(clz.getClassLoader());

System.out.println("==== Class Loader Tree ====");
ClassLoader cl=myLoader;
while(cl!=null){
    System.out.println(cl);
    cl=cl.getParent();
}

结果:

ava.io.FileNotFoundException: D:\tmp\clz\java\lang\Object.class (系统找不到指定的路径。)
	at java.io.FileInputStream.open(Native Method)
	.....
	at geym.jvm.ch6.classloader.ClassLoaderTest.main(ClassLoaderTest.java:7)
无法载入类:java.lang.Object需要请求父加载器
geym.jvm.ch6.classloader.OrderClassLoader@18f5824
==== Class Loader Tree ====
geym.jvm.ch6.classloader.OrderClassLoader@18f5824
sun.misc.Launcher$AppClassLoader@f4f44a
sun.misc.Launcher$ExtClassLoader@1d256fa

因为所有的类都继承自Object,前面知道验证的过程需要先检查自己的父类是否加载,先从OrderClassLoader加载,即从文件中加载Object,找不到Object,之后使用appLoader加载Object

DemoA在ClassPath中,但由OrderClassLoader加载,而不是由APPClassLoader加载

如果OrderClassLoader不重载loadClass(),只重载findClass,还是双亲委派机制,那么程序输出为

sun.misc.Launcher$AppClassLoader@b23210
==== Class Loader Tree ====
geym.jvm.ch6.classloader.OrderClassLoader@290fbc
sun.misc.Launcher$AppClassLoader@b23210
sun.misc.Launcher$ExtClassLoader@f4f44a

DemoA由AppClassLoader加载,Object也能加载到了

热替换

  • 含义:

    当一个class被替换后,系统无需重启,替换的类立即生效

    php就是热替换的。

    例子:

    geym.jvm.ch6.hot.CVersionA
public class CVersionA {
	public void sayHello() {
		System.out.println("hello world! (version A)");
	}
}
  • DoopRun 不停调用CVersionA . sayHello()方法,因此有输出:

    hello world! (version A)
  • 在DoopRun 的运行过程中,替换CVersionA 为:
public class CVersionA {
	public void sayHello() {
		System.out.println("hello world! (version B)");
	}
}
  • 替换后, DoopRun 的输出变为

    hello world! (version B)

    思考:应该如何做?

JVM内核-原理、诊断与优化学习笔记(六):类装载器的更多相关文章

  1. 深入JVM内核---原理,诊断与优化

    JVM的概念 JAM是Java Virtual Machine的简称.意为Java虚拟机 虚拟机 指通过软件模拟的具有完整硬件系统功能的,运行在一种完整隔离环境中的完整计算机系统 有哪些虚拟机 - V ...

  2. JVM内核-原理、诊断与优化学习笔记(八):JAVA堆分析

    文章目录 内存溢出(OOM)的原因 在JVM中,有哪些内存区间? 堆溢出 永久区 Java栈溢出 直接内存溢出 小问题? MAT使用基础 柱状图显示 支配树 显示线程信息 显示堆总体信息,比如消耗最大 ...

  3. JVM内核-原理、诊断与优化学习笔记(七):性能监控工具

    文章目录 系统性能监控 系统性能监控- linux uptime top vmstat(虚拟内存统计) pidstat 系统性能监控 - windows 任务管理器 Perfmon Process E ...

  4. JVM内核-原理、诊断与优化学习笔记(四):GC算法与种类

    文章目录 GC的概念 GC算法 引用计数法 引用计数法的问题 标记清除 标记压缩 小问题 复制算法 复制算法的最大问题是:空间浪费 整合标记清理思想 -XX:+PrintGCDetails的输出 gc ...

  5. JVM内核-原理、诊断与优化学习笔记(三):常用JVM配置参数

    文章目录 Trace跟踪参数 -verbose:gc (打开gc的跟踪情况) -XX:+printGC(打开gc的log开关,如果在运行的过程中出现了gc,就会打印出相关的信息.) -XX:+Prin ...

  6. JVM内核-原理、诊断与优化学习笔记(二):JVM运行机制

    文章目录 JVM启动流程 PC寄存器 方法区 保存装载的类信息 通常和永久区(Perm)关联在一起 Java堆 Java栈 Java栈 – 局部变量表 ** 包含参数和局部变量 ** Java栈 – ...

  7. JVM内核-原理、诊断与优化学习笔记(一):初识JVM

    文章目录 JVM的概念 JVM是Java Virtual Machine的简称.意为Java虚拟机 虚拟机 有哪些虚拟机 VMWare或者Visual Box都是使用软件模拟物理CPU的指令集 JVM ...

  8. JVM内核-原理、诊断与优化学习笔记(十一):JVM字节码执行

    文章目录 javap javap 举个

  9. JVM内核-原理、诊断与优化学习笔记(十):Class文件结构

    文章目录 语言无关性 文件结构 魔数 版本 常量池 CONSTANT_Utf8 CONSTANT_Integer CONSTANT_String CONSTANT_NameAndType CONSTA ...

随机推荐

  1. thinkphp 图形处理

    使用Think\Image类进行图像处理功能,支持Gd库和Imagick库,包括对GIf图像处理的支持. 实例化类库 $image = new \Think\Image(); 默认使用GD库进行图像操 ...

  2. Python每日一题 001

    Github地址:https://github.com/Yixiaohan/show-me-the-code Talk is Cheap, show me the code. --Linus Torv ...

  3. Windows 驱动模型的发展历史

    直接从win95/98说起,因为之前的系统基本上没有保护模式的概念,程序员可以直接修改任意内存的数据.在95/98中采用的内核开发模型是VxD(虚拟设备驱动),在dos时期,程序认为它们拥有系统的一切 ...

  4. (16)centos7 日志文件

    常见日志文件 开机启动日志,只会记录本次信息 /var/log/boot.log 计划任务日志 /var/log/cron 开机内核检测信息 /var/log/dmesg 账号登录信息 /var/lo ...

  5. 【AI图像识别一】人脸识别测试探索

    ****************************************************************************** 本文主要介绍AI能力平台的人脸识别技术的测 ...

  6. 卷积神经网络学习笔记(CNN)

    看了很多关于卷积神经网络的论文和资料 可是我发现一个问题,就是pooling会出现一个问题 我能找到的代码里面都是用的均值子采样,如果改成最大或最小,或P范数都会在学习训练的时候遇到不知道怎么处理的问 ...

  7. 5-MySQL-Ubuntu-操作数据库的基本操作语句

    注意: (1)每一条sql语句都是以分号(;)结尾! (2)数据库的默认charset不支持中文,所以每次在创建数据库的时候要指定字符集charset=utf8; (一) 查看当前时间: select ...

  8. winform 窗体拖动

    winform 由于自带的界面太丑,有时候就想着去掉标题栏,自己写,自己做UI 但是发现没法拖动了,或者,有时候我们也想让整个窗体都能够随着鼠标进行拖动,来来来,看下面 #region 让窗口可以随意 ...

  9. vue axios----基于 Promise 的 HTTP 请求

    vue axiosvue2.0之axios接口請求管理功能特性axios API開始使用get請求post请求多个请求并发拦截器移除一个拦截器:自定义的 axios 实例添加拦截器:vue2.0之ax ...

  10. Shell脚本 全局变量、局部变量

    在不同的作用域中,同名的变量不会相互干涉,就好像 A 班有个叫小明的同学,B 班也有个叫小明的同学,虽然他们都叫小明(对应于变量名),但是由于所在的班级(对应于作用域)不同,所以不会造成混乱.但是如果 ...