热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

类加载机制Java虚拟机第二步

类加载机制概述本文所有代码和介绍,基于JDK1.8.0.25还是放上这个最眼熟的图,

类加载机制概述


本文所有代码和介绍,基于 JDK 1.8.0.25


还是放上这个最眼熟的图,这个针对 hotspot 虚拟机所绘制的简图:

JVM运行时数据区

本文要介绍的就是这个图中的 类加载器 ,主要内容包括类加载器的工作步骤,内部组成等。

对于类加载器的内部结构,先看下面这个图:

类加载子系统内部结构

类加载器会把 .class 字节码加载到运行时数据区的方法区。

除了类的信息外,方法区还存放着运行时常量池信息(版本、字段、方法、接口啥的)。


class 文件生命周期

从上面的图中可以看出:


.class 文件在类加载器的执行过程包括了 加载验证准备解析初始化 五个阶段。


再加上运行时数据区、执行引擎和最后消亡,根据顺序如下图所示:

class文件生命周期


Loading:加载阶段

加载阶段有不同的类加载器,当然也可以自定义类加载器。

不过这是在自建虚拟机时、或者大佬优化虚拟机才搞的,我们就知道还能自定义就行了。

类的加载分为以下步骤:



  1. 通过一个类的全限定名获取定义此类文件的二进制字节流;

  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;

  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

Class加载步骤简图

获取 .class 文件的几种方式:



  • 从本地系统中直接加载

  • 通过网络下载 .class 文件,这种场景最典型的应用就是 Web Applet

  • 从 zip,jar, war 等归档文件中加载 .class 文件

  • 运行时计算生成,使用最多的是动态代理技术。在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass() 来为特定接口生成形式为 “*$Proxy” 的代理类的二进制字字符流

  • 从专有数据库中提取 .class 文件

  • 从其他文件生成,典型应用场景就是 JSP ,由 JSP 文件生成 Class 文件


Linking:链接阶段

主要包括以下三个阶段:



  • 验证: 确保被加载的类的正确性



    1. 目的在于确保 class 文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。



    2. 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。



























      验证方式具体验证内容和措施
      文件格式验证验证字节流是否符合 Class 文件格式的规范;
      1. 是否以0xCAFEBABE开头
      2. 主、次版本号是否在当前虚拟机的处理范围之内
      3. 常量池中的常量是否有不被支持的类型
      4. 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
      5. Class 文件各个部分及文件本身是否有被删除的或附加的其他信息
      元数据验证对字节码描述的信息进行语义分析,保证描述信息符合规范要求,
      1. 这个类是否有父类,除了java.lang.Object之外
      2. 这个类的父类是否继承了不允许被继承的类(final 修饰)
      3. 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
      字节码验证通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
      符号引用验证确保解析动作能正确执行。



    验证阶段非常重要,这个阶段直接决定了 JVM 是否能承受恶意代码的攻击;这个阶段占用了整个类加载阶段的大量时间。

    但是验证阶段不是必须的,它对 JVM 运行没有影响,如果所引用的类经过反复验证,那么可以考虑采用 -Xverifynone 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。






  • 准备: 为类的静态变量分配内存,并将其初始化为默认值



    1. 为类变量分配内存并且设置该类变量的默认初始值,即零值。

    2. 这里不包含用 final 修饰的 static,因为 final 在编译的时候就会分配了,准备阶段会显式初始化。

    3. 这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到 Java 堆中。(因为这时候还没创建对象)

    例如以下代码:

    private static int x = 1;

    在准备阶段,它只会被赋值为0,在初始化阶段才会赋值为1.


    不同的类型零值不同:











































    数据类型零值数据类型零值
    int0booleanfalse
    long0Lfloat0.0f
    short(short)0double0.0d
    char‘\u0000’referencenull
    byte(byte)0




  • 解析: 把类中的符号引用转换为直接引用

    1. 将常量池内的符号引用转换为直接引用的过程。

    2. 事实上,解析操作往往会伴随着JM在执行完初始化之后再执行。

    3. 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

    4. 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info、 CONSTANT_Fleldref_info、 CONSTANT_Methodref_info等。




Initialization:初始化阶段


初始化阶段就是执行类构造器方法 () 的过程。

这个方法是 javac 编译器自动收集类中的所有变量的赋值动作和静态代码块中的语句合并而成的。


编译器收集顺序和语句在源文件中出现顺序相同

所以意思就是,static 代码块在初始化阶段就已经执行了。


使用和卸载

使用时,类访问方法区内的数据结构的接口, 对象是 Heap 区的数据。

Java虚拟机将结束生命周期的几种情况



  • 执行了 System.exit() 方法

  • 程序正常执行结束

  • 程序在执行过程中遇到了异常或错误而异常终止

  • 由于操作系统出现错误而导致Java虚拟机进程终止


类加载器的种类和特点

在上面的加载阶段图中,已经画出了: 启动(引导)类加载器扩展类加载器应用类加载器 ,还说明了可以 自定义加载器

但是,但是,Java 虚拟机规范只把加载器分为了两种:启动类加载器(C++语言实现的,很特别哦)和自定义类加载器。

它把派生于 public abstract class ClassLoader 的加载器都归类为了自定义类加载器。

类加载器层次结构

这个结构层次图也被成为 双亲委派模型 ,具体工作原理后面再讲,先看几个加载器的作用:



  1. 启动类加载器 : Bootstrap ClassLoader,负责加载存放在 JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。

  2. 扩展类加载器 : Extension ClassLoader,该加载器由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

  3. 应用程序类加载器 : Application ClassLoader,该类加载器由 sun.misc.Launcher$AppClassLoader 来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:



  • 在执行非置信代码之前,自动验证数字签名。

  • 动态地创建符合用户特定需要的定制化构建类。

  • 从特定的场所取得java class,例如数据库中和网络中。


ClassLoader 体验

先做个小实验:

public class JvmTest1 {
public static void main(String[] args) {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
System.out.println(systemClassLoader.getParent());
System.out.println(systemClassLoader.getParent().getParent());
ClassLoader classLoader = JvmTest1.class.getClassLoader();
System.out.println(classLoader);
}
}

输出效果:

sun.misc.Launcher$AppClassLoader@58644d46
sun.misc.Launcher$ExtClassLoader@66d3c617
null
sun.misc.Launcher$AppClassLoader@58644d46

可见我们写的代码,都是默认使用系统类加载器进行加载。

在 Class 源码中,获取 ClassLoader 是通过 native 方法:

public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}
return cl;
}
native ClassLoader getClassLoader0();


类加载器双亲委派机制


双亲委派模型工作流程为:类加载器收到加载请求时,会首先委派父加载器去加载,逐层向上委派直到父加载器反馈无法完成加载,才由子加载器完成加载。


protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 首先判断该类型是否已经被加载
Class c = findLoadedClass(name);
if (c == null) {
//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
try {
if (parent != null) {
//如果存在父类加载器,就委派给父类加载器加载
c = parent.loadClass(name, false);
} else {
//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}

双亲委派优势



  • 系统类防止内存中出现多份同样的字节码

  • 保证Java程序安全稳定运行


参考文章

https://www.pdai.tech/md/java/jvm/java-jvm-classload.html

https://www.bilibili.com/video/BV1PJ411n7xZ



推荐阅读
  • OpenCV4.5.0+contrib编译流程及解决错误方法
    本文介绍了OpenCV4.5.0+contrib的编译流程,并提供了解决常见错误的方法,包括下载失败和路径修改等。同时提供了相关参考链接。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • Redis底层数据结构之压缩列表的介绍及实现原理
    本文介绍了Redis底层数据结构之压缩列表的概念、实现原理以及使用场景。压缩列表是Redis为了节约内存而开发的一种顺序数据结构,由特殊编码的连续内存块组成。文章详细解释了压缩列表的构成和各个属性的含义,以及如何通过指针来计算表尾节点的地址。压缩列表适用于列表键和哈希键中只包含少量小整数值和短字符串的情况。通过使用压缩列表,可以有效减少内存占用,提升Redis的性能。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • Java程序设计第4周学习总结及注释应用的开发笔记
    本文由编程笔记#小编为大家整理,主要介绍了201521123087《Java程序设计》第4周学习总结相关的知识,包括注释的应用和使用类的注释与方法的注释进行注释的方法,并在Eclipse中查看。摘要内容大约为150字,提供了一定的参考价值。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • Hibernate延迟加载深入分析-集合属性的延迟加载策略
    本文深入分析了Hibernate延迟加载的机制,特别是集合属性的延迟加载策略。通过延迟加载,可以降低系统的内存开销,提高Hibernate的运行性能。对于集合属性,推荐使用延迟加载策略,即在系统需要使用集合属性时才从数据库装载关联的数据,避免一次加载所有集合属性导致性能下降。 ... [详细]
author-avatar
hongxiaochen8846_792
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有