JVM(五)——类加载阶段

一、类加载阶段

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载
(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化
(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称
为连接(Linking)。
类的声明周期
加载、验证、准备、初始化和卸载阶段的加载顺序是确定的,而解析阶段不一定:它在某些情况下可以在初始化阶段之后再开始。这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。

1)加载

“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段。

  • 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:
    • _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴
    • 露给 java 使用
    • _super 即父类
    • _fields 即成员变量
    • _methods 即方法
    • _constants 即常量池
    • _class_loader 即类加载器
    • _vtable 虚方法表
    • _itable 接口方法表

注意:

  • instanceKlass 这样的【元数据】是存储在方法区(1.8 后的元空间内),但 _java_mirror是存储在堆中的
  • 可以通过前面介绍的 HSDB 工具查看

在这里插入图片描述

2)连接

2.1 验证

验证类是否符合 JVM 规范,安全性检查。

2.2 准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段(默认值)。这里仅包括类变量,而不包括实例变量。

  • 设置默认值static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾
  • static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,真正的赋值在初始化阶段完成
  • 如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶
    段完成
  • 如果 static 变量被 final 修饰,但属于引用类型,那么赋值也会在初始化阶段完成

2.3 解析

将常量池中的符号引用解析为直接引用

package cn.itcast.jvm.t3.load;
/**
* 解析的含义
*/
public class Load2 {public static void main(String[] args) throws ClassNotFoundException, IOException {ClassLoader classloader = Load2.class.getClassLoader();// loadClass 方法不会导致类的解析和初始化 创建了C的对象,但不会创建 D的对象Class<?> c = classloader.loadClass("cn.itcast.jvm.t3.load.C");// 创建了 C 和 D的对象// new C();	System.in.read();}
}
class C {D d = new D();
}
class D {
}

3)初始化阶段

<clinit>()V 方法
初始化即调用 <clinit>()V ,虚拟机会保证这个类的『构造方法』的线程安全
发生的时机
类初始化是【懒惰的】

  • main 方法所在的类,总会被首先初始化
  • 首次访问这个类的静态变量或静态方法时
  • 子类初始化,如果父类还没初始化,会引发
  • 子类访问父类的静态变量,只会触发父类的初始化
  • Class.forName
  • new 会导致初始化

不会导致类初始化的情况

  • 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
  • 类对象.class 不会触发初始化
  • 创建该类的数组不会触发初始化
  • 类加载器的 loadClass 方法
  • Class.forName 的参数 2 为 false 时

二、类加载器

以 JDK 8 为例:

名称加载哪的类说明
Bootstrap ClassLoaderJAVA_HOME/jre/lib无法直接访问
Extension ClassLoaderJAVA_HOME/jre/lib/ext上级为 Bootstrap, 显示 null
Application ClassLoaderclasspath上级为 Extension
自定义类加载自定义上级为 Application

一般情况下 获取的 classloader 默认是 AppClassLoader 应用程序类加载器

1)启动类加载器

E:\git\jvm\out\production\jvm>java -Xbootclasspath/a:. cn.itcast.jvm.t3.load.Load5
bootstrap F init
null
  • -Xbootclasspath 表示设置 bootclasspath (Bootstrap ClassLoader)
  • 其中 /a:. 表示将当前目录追加至 bootclasspath 之后

2)扩展类加载器

将A类打包成 jar包,放在 JAVA_HOME/jre/ext目录下,获取A类的类加载器为 sun.misc.Launcher$ExtClassLoader$@23232
通过双亲委派机制,往上找类加载器,委派上级优先查找加载,如果上级没有,最后由本级加载(应用程序类加载器)

3)双亲委派模式

所谓的双亲委派,就是指调用类加载器的 loadClass 方法时,查找类的规则

注意
这里的双亲,翻译为上级似乎更为合适,因为它们并没有继承关系

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 1. 检查该类是否已经加载Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {// 2. 有上级的话,委派上级 loadClassc = parent.loadClass(name, false);} else {// 3. 如果没有上级了(ExtClassLoader),则委派 BootstrapClassLoaderc = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}if (c == null) {long t1 = System.nanoTime();// 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载c = findClass(name);// 5. 记录耗时sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

4)线程上下文类加载器

我们在使用 JDBC 时,都需要加载 Driver 驱动,但是实际上我们不写Class.forName("com.mysql.jdbc.Driver"),也可以让com.mysql.jdbc.Driver正确加载。底层是

public class DriverManager {// 注册驱动的集合private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();// 初始化驱动static {loadInitialDrivers();println("JDBC DriverManager initialized");}

进入loadInitialDrivers() 方法

private static void loadInitialDrivers() {...// 1)使用 ServiceLoader 机制加载驱动,即 SPIAccessController.doPrivileged(new PrivilegedAction<Void>() {publicVoid run() {ServiceLoader<Driver> loadedDrivers =ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});println("DriverManager.initialize: jdbc.drivers = " + drivers);// 2)使用 jdbc.drivers 定义的驱动名加载驱动if (drivers == null || drivers.equals("")) {return;}String[] driversList = drivers.split(":");println("number of Drivers:" + driversList.length);for (String aDriver : driversList) {try {println("DriverManager.Initialize: loading " + aDriver);// 这里的 ClassLoader.getSystemClassLoader() 就是应用程序类加载器Class.forName(aDriver, true,	ClassLoader.getSystemClassLoader());} catch (Exception ex) {println("DriverManager.Initialize: load failed: " + ex);}}
}

底层实际上是 spi(Service Provider Interface)机制。
最后是使用 Class.forName 完成类的加载和初始化,关联的是应用程序类加载器,因此可以顺利完成类加载

约定如下,在 jar 包的 META-INF/services 包下,以接口全限定名名为文件,文件内容是实现类名称
在这里插入图片描述
体现的是【面向接口编程+解耦】的思想,在下面一些框架中都运用了此思想:

  • JDBC
  • Servlet 初始化器
  • Spring 容器
  • Dubbo(对 SPI 进行了扩展)

5)自定义类加载器

在这里插入图片描述
在自定义类加载器时,使用不同的类加载器对象获取的类不是相同的。
例如:

public class Load7 {public static void main(String[] args) throws Exception {MyClassLoader classLoader = new MyClassLoader();Class<?> c1 = classLoader.loadClass("MapImpl1");Class<?> c2 = classLoader.loadClass("MapImpl1");	// 都是 classLoader1 对象System.out.println(c1 == c2);	// trueMyClassLoader classLoader2 = new MyClassLoader();Class<?> c3 = classLoader2.loadClass("MapImpl1");	// 创建了 classLoader2 System.out.println(c1 == c3);	// false}
}

三、运行期优化

1)即时编译

分层编译

JVM 将执行状态分成了 5 个层次:

  • 0 层,解释执行(Interpreter)
  • 1 层,使用 C1 即时编译器编译执行(不带 profiling)
  • 2 层,使用 C1 即时编译器编译执行(带基本的 profiling)
  • 3 层,使用 C1 即时编译器编译执行(带完全的 profiling)
  • 4 层,使用 C2 即时编译器编译执行

即时编译器:c1 客户端编译器(Client Compiler) c2 服务端编译器 (Server Compiler)

profiling 是指在运行过程中收集一些程序执行状态的数据,例如【方法的调用次数】,【循环的
回边次数】等

即时编译器(JIT)与解释器的区别

  • 解释器是将字节码解释为机器码,下次即使遇到相同的字节码,仍会执行重复的解释
  • JIT 是将一些字节码编译为机器码,并存入 Code Cache,下次遇到相同的代码,直接执行,无需再编译
  • 解释器是将字节码解释为针对所有平台都通用的机器码
  • JIT 会根据平台类型,生成平台特定的机器码

对于占据大部分的不常用的代码,我们无需耗费时间将其编译成机器码,而是采取解释执行的方式运行;另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,以达到理想的运行速度。 执行效率上简单比较一下 Interpreter < C1 < C2,总的目标是发现热点代码(hotspot名称的由来),优化之

优化手段称之为【逃逸分析】,发现新建的对象是否逃逸。可以使用 -XX:-DoEscapeAnalysis 关闭逃逸分析,再运行刚才的示例观察结果,未使用 C2 即时编译执行。

方法内联

对热点方法(经常调用的方法),且代码长度较短,会进行内联,即将方法内的代码拷贝、粘贴到方法调用者的位置。

-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining (解锁隐藏参数)打印 inlining(内联) 信息
-XX:CompileCommand=dontinline,*JIT2.square 禁止某个方法 inlining
-XX:+PrintCompilation 打印编译信息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/573015.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

循环神经网络之序列模型

自回归模型 自回归模型&#xff1a; 只与x 有关 &#xff0c;对自己执行回归隐变量自回归&#xff1a;与X 和过去观测总结h 都有关 案例 %matplotlib inline import torch from torch import nn from d2l import torch as d2lT 1000 # 总共产生1000个点 time torch.aran…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之七 简单水彩画效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之七 简单水彩画效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单实战案例 之七 简单水彩画效果 一、简单介绍 二、简单图像浮雕效果实现原理 三、简单水彩画效果案例实现简单步骤 四、注意事项…

【搜索引擎2】实现API方式调用ElasticSearch8接口

1、理解ElasticSearch各名词含义 ElasticSearch对比Mysql Mysql数据库Elastic SearchDatabase7.X版本前有Type&#xff0c;对比数据库中的表&#xff0c;新版取消了TableIndexRowDocumentColumnmapping Elasticsearch是使用Java开发的&#xff0c;8.1版本的ES需要JDK17及以上…

五、Elasticsearch 集成

目录 5.1 Spring Data 框架集成5.1.1 Spring Data 框架介绍5.1.2 Spring Data Elasticsearch 介绍5.1.3 Spring Data Elasticsearch 版本对比5.1.4 集成步骤 5.1 Spring Data 框架集成 5.1.1 Spring Data 框架介绍 Spring Data 是一个用于简化数据库开发的开源框架。其主要目…

springboot+vue考试管理系统

基于springboot和vue的考试管理系统 001 springboot vue前后端分离项目 本文设计了一个基于Springbootvue的前后端分离的在线考试管理系统&#xff0c;采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

[2021]Zookeeper getAcl命令未授权访问漏洞概述与解决

今天在漏洞扫描的时候蹦出来一个zookeeper的漏洞问题&#xff0c;即使是非zookeeper的节点&#xff0c;或者是非集群内部节点&#xff0c;也可以通过nc扫描2181端口&#xff0c;获取极多的zk信息。关于漏洞的详细描述参考apache zookeeper官方概述&#xff1a;CVE-2018-8012: A…

服务器呀服务器,一个虚拟专用服务器的使用教程

目前刚接触服务器这一块的学习&#xff0c;这里记录一下解如何获取自己的第一台虚拟云服务器&#xff0c;给刚入行服务器开发的小伙伴做一个参考。 具体的步骤如下&#xff1a; 一、服务器的注册和获取 1、打开bwg88服务器平台地址&#xff1a;点击进入 https://bwh88.net/a…

自动生成测试位置吸附脚本设计思路

前言 计算一个异质结需要测试对比不同吸附位置之间的能量差异&#xff0c;可以直接手动建模&#xff0c;但是人太懒了&#xff0c;能交给机器的自己就别动手 问题描述 如图上所示是我计算吸附用的衬底&#xff0c;当原子在上面吸附时我考虑了25个&#xff08;可以随便取&…

Xilinx IDDR及ODDR使用和仿真

平台&#xff1a;Vivado2018 官方相关文档&#xff0c;ug471_7Series_SelectIO.pdf 关于IDDR与ODDR Input DDR Resource(IDDR) 外部的数据在时钟的上下沿同时传输数据&#xff0c;我们可以使用IDDR原语将输入的单bit数据转化为2bit的数据输出。同时数据速率变为原来的二分之一…

hadoop 查询hdfs资源信息的方式

hdfs dfsadmin -report &#xff3b;-live&#xff3d;&#xff3b;-dead&#xff3d;&#xff3b;-decommissioning&#xff3d;

OSCP靶场--GLPI

OSCP靶场–GLPI 考点(CVE-2022-35914 php执行函数绕过ssh端口转发jetty xml RCE) 1.nmap扫描(ssh端口转发) ## ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.194.242 -sV -sC --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-26 22:22 EDT Nmap…

基于Rflysim平台的无人机拦截三维比例导引算法仿真

【后厂村路钢铁侠出品】 一、Rflysim简介 RflySim是一套专为科研和教育打造的Pixhawk /PX4 和MATLAB/Simulink生态系统或工具链&#xff0c;采用基于模型设计&#xff08;Model-Based Design&#xff0c; MBD&#xff09;的思想&#xff0c;可用于无人系统的控制和安全测试。…