Java类加载

2023-10-30

类加载

过程

请添加图片描述
将字节码load到虚拟机中的过程称为类的加载

  1. 加载 ,load字节码
  2. 验证 ,包括文件格式、元数据、字节码、符号引用的校验等;
  3. 准备 ,包括类变量初始化赋值:基本类型0、引用类型null、常量=类中赋值;
  4. 解析 ,符号引用换直接引用、静态绑定等;
  5. 初始化 ,静态变量赋值、执行静态代码块等;
  6. 卸载 ,用户自定义的类的Class对象不再被引用时,类在方法区内的数据会被卸载;

对象的创建

  1. 堆分配对象的内存空间;
  2. 在栈中定义变量,将内存地址给变量
  3. 对象变量赋值(方法区类的信息);
  4. 初始化,先基类后子类,先执行实例代码块后构造器;

类加载器

请添加图片描述

双亲委派

  1. 保证安全性 ,防止重复加载;
  2. 保证唯一 ,核心class不被篡改,但可以扔到<HAVA_HOME>\lib目录;

打破双亲委派

常见以tomcat和SPI为例
tomcat
  1. 隔离不同应用 ,例如版本不同的两个应用:Spring2.5 和 3.0;
  2. 灵活性 ,多web修改文件,不会互相影响;
  3. 性能 ,使用相同的类用common类加载器,不同的话用各自的类加载器
SPI
  1. classpath/META-INF/service下查文件
  2. ServerClassLoader加载

执行

解释执行 or 编译执行

请添加图片描述
解释:输入程序代码–>得到结果
编译:输入程序代码–>得到可执行代码–>执行可执行的代码得到结果

关于编译

JVM在执行代码的时候并不立即编译代码,主要有个原因:
1.有些代码可能执行频率比较低,甚至就只运行一次,这种情况下,将代码翻译成java字节码比编译这段代码并运行来说要快得多。
2.当 JVM 执行某一方法或遍历循环的次数越多,就会更加了解代码结构,那么 JVM 在编译代码的时候就做出相应的优化。

在主流商用JVM(HotSpot、J9)中,Java程序一开始是通过解释器(Interpreter)进行解释执行的。当JVM发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“热点代码(Hot Spot Code)”,然后JVM会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为:即时编译器(Just In Time Compiler,JIT)

JIT编译器与解释器的工作模式

1、混合模式(Mixed Mode)
JIT编译器(无论C1还是C2)与解释器配合工作的方式;
这是默认的方式,也可通过“-Xmixed”参数设定;

2、解释模式(Interpreted Mode)
全部代码由解释器解释执行,JIT编译器不介入工作;
可以通过“-Xint”参数设定;

3、编译模式(Compiler Mode)
优先采用编译方式执行程序,但解释器仍要在编译无法时行时介入执行过程;
可以通过“-Xcomp”参数设定;
该参数强调的是首次调用方法时执行编译,并不是不用解释器;
一般情况下(不开启分层编译),一个方法需要解释执行一定次数后才编译;

JDK8作为默认开启分层编译策略;
可以通过java -version来查看工作模式
在这里插入图片描述

JIT编译器

Java 程序最初是仅仅通过解释器解释执行的,即对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。于是后来在虚拟机中引入了 JIT 编译器(即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是 JIT 编译器。

HotSpot虚拟机内置两个即时编译器,分别Client Compiler和Server Comiler,如下:

1、Client Compiler

    简称C1编译器;

(A)、应用特点

   较为轻量,只做少量性能开销比较高的优化,它占用内存较少,适合于桌面交互式应用;

(B)、优化技术

    它是一个简单快速的三段式编译器;

    主要关注点在于局部性的优化,而放弃了许多耗时较长的全局优化;

    在寄存器分配策略上,JDK6以后采用的为线性扫描寄存器分配算法,其他方面的优化,主要有方法内联、去虚拟化、冗余消除等;

(C)、设置参数

    可以使用"-client"参数强制选择运行在Client模式(Client VM);

(D)、编译过程

  它是一个简单快速的三段式编译器,主要关注点在于局部性的优化,而放弃了许多耗时较长的全局优化;

   三段式编译过程如下

在这里插入图片描述

(1)、在字节码上进行一些基础优化,如方法内联、常量传播等;

   然后将字节码构造成一种高级中间代码表示(High-Level Intermediate Representaion,HIR);

   HIR使用静态单分配(SSA)的形式表示代码值;

(2)、在HIR基础上再次进行一些优化,空值检查消除、范围检查消除等;

   然后将HIR转换为LIR(低级中间代码表示)

(3)、在LIR基础上分配寄存器、做窥孔优化,然后生成机器码;

2、Server Compiler

    简称C2编译器,也叫Opto编译器;

(A)、应用特点

   较为重量,采用了大量传统编译优化的技巧来进行优化,占用内存相对多一些,适合服务器端的应用;

(B)、优化技术

    它会执行所有经典的优化动作,如无用代码消除、循环展开、循环表达式外提、消除公表达式、常量传播、基本块重排序等;

    还会一些与Java语言特性密切相关的优化技术,如范围检查消除、空值检查消除等;

    另外,还进行一些不稳定的激进优化,如守护内联、分支频率预测等;

(C)、收集性能信息

    由于C2会收集程序运行信息,因此其优化范围更多在于全局优化,不仅仅是一个方块的优化;

    收集的信息主要有:分支的跳转/不跳转的频率、某条指令上出现过的类型、是否出现过空值、是否出现过异常等。

(D)、与C1的不同点

    和C1的不同主要在于寄存器分配策略及优化范围,寄存器分配策略上C2采用传统的全局图着色寄存器分配算法;

    C2编译速度较为缓慢,但远远超过传统的静态优化编译器;

    而且编译输出的代码质量高,可以减少本地代码的执行时间;

(E)、设置参数

    可以使用"-server"参数强制选择运行在Server模式(Server VM);

分层编译

为了在程序启动响应速度与运行效率之间达到最佳平衡,会启用分层编译(Tiered Compilation)策略;
1、编译层次

   根据编译器编译、优化的规模与耗时,划分出不同的编译层次,包括:

(I)、第0层

   程序解释执行,解释器不开启性能监控功能(Profiling),可触发第1层编译;

(II)、第1层

   也称为C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要加入性能监控的逻辑;

(III)、第2层

   也称为C2编译,也是将字节码编译为本地代码,但进行一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化;

2、优点

   这时C1和C2同时进行工作,许多代码都可能被编译多次;

   用C1获取更高的编译速度,用C2获取更好的编译质量;

   解释器执行时也无须再承担收集性能监控信息的任务(如果不开启分层编译,又工作在Server模式,解释器提供监控信息给C2使用);

   最终在程序启动响应速度与运行效率之间达到最佳平衡;

3、设置参数

   JDK6开始出现,需要“-XX:+TieredCompilation”指定开启;

   JDK8作为默认的策略,可以通过“-XX:-TieredCompilation”关闭策略;

   注意,只能在Server模式下使用;

热点监测

上边说只有热点代码才会被编译成机器码,什么样的代码会认为是热点代码?达到什么样的标准就会被认为是热点代码呢?

热点代码

JIT编译对象为"热点代码",包括两类:

1、被多次调用的方法

   由方法调用触发的编译,以整个方法体为编译对象;

   JVM中标准的的JIT编译方式;

2、被多次执行的循环体

   由循环体触发,仍然以整个个方法体为编译对象;

   发生在方法执行过程中,方法栈帧还在栈上,方法就被替换;

   称为栈上替换(On Stack Replacement),简称OSR编译;
热点监测

1.基于采样的热点探测

采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这段方法代码就是“热点代码”。

优点:这种探测方法的好处是实现简单高效,还可以很容易地获取方法调用关系

缺点:很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测

2.基于计数器的热点探测

采用这种方法的虚拟机会为每个方法,甚至是代码块建立计数器,统计方法的执行次数,如果执行次数超过一定的阀值,就认为它是“热点方法”。

优点:更加精确和严谨

缺点:这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系

在 HotSpot 虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器

方法调用计数器

方法调用计数器用来统计方法调用的次数,在默认设置下,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间内方法被调用的次数。

回边计数器

回边计数器用于统计一个方法中循环体代码执行的次数(准确地说,应该是回边的次数,因为并非所有的循环都是回边),在字节码中遇到控制流向后跳转的指令就称为“回边”。

阈值设置

方法调用计数器

默认C1时为1500次(sparc平台才是1000),C2时为10000次;

可以通过"-XX:CompileThreshold"参数设定;

启用分层编译时将忽略此选项,请参阅选项"-XX:+ TieredCompilation";

回边计数器

C1:

计算规则:方法调用计数器阈值(CompileThreshold)*OSR比率(OnStackReplacePercentage)/100;

   默认:OnStackReplacePercentage=933, CompileThreshold=1500,计算阈值为14895;

C2:

前面介绍分层编译时曾说:如果不开启分层编译,又工作在Server模式,解释器提供监控信息给C2使用,所以多了个解释器监控比率(InterpreterProfilePercentage);

   计算规则:CompileThreshold*(OnStackReplacePercentage-InterpreterProfilePercentage)/100;

   默认:OnStackReplacePercentage=140, CompileThreshold=10000,InterpreterProfilePercentage=33,计算阈值为10700;

codeCahe

Java代码在执行时一旦被编译器编译为机器码,下一次执行的时候就会直接执行编译后的代码,也就是说,编译后的代码被缓存了起来。缓存编译后的机器码的内存区域就是codeCache。这是一块独立于java堆之外的内存区域。除了jit编译的代码之外,java所使用的本地方法代码(JNI)也会存在codeCache中。不同版本的jvm、不同的启动方式codeCache的默认大小也不同。

JVM 版本和启动方式

默认 codeCache大小

在这里插入图片描述

我们现在线上所使用的大多数都是JDK8 64位 Server模式,codeCache空间是240M,随着时间推移,会有越来越多的方法被编译,codeCache使用量会逐渐增加,直至耗尽。在codeCache满了之后会发生什么?

在jdk1.7.0_4之前,你会在jvm的日志里看到这样的输出:

Java HotSpot™ 64-Bit Server VM warning: CodeCache is full. Compiler has been disabled.

Jit编译器被停止了,并且不会被重新启动。已经被编译过的代码仍然以编译方式执行,但是尚未被编译的代码就只能以解释方式执行了。

针对这种情况,jvm提供了一种比较激进的codeCache回收方式:Speculative flushing。在jdk1.7.0_4之后这种回收方式默认开启,而之前的版本需要通过一个启动参数来开启:-XX:+UseCodeCacheFlushing。在Speculative flushing开启的情况下,当codeCache将要耗尽时,最早被编译的一半方法将会被放到一个old列表中等待回收。在一定时间间隔内,如果方法没有被调用,这个方法就会被从codeCache充清除。

很不幸的是,在jdk1.7中,当codeCache耗尽时,Speculative flushing释放了一部分空间,但是从编译日志来看,jit编译并没有恢复正常,并且系统整体性能下降很多,出现大量超时。在oracle官网上看到这样一个bug:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8006952 由于codeCache回收算法的问题,当codeCache满了之后会导致编译线程无法继续,并且消耗大量cpu导致系统运行变慢。Bug里影响版本是jdk8,但是从网上其他地方的信息看,jdk7应该也存在相同的问题,并且没有被修复。

codeCache调优

以client模式或者是分层编译模式运行的应用,由于需要编译的类更多(C1编译器编译阈值低,更容易达到编译标准),所以更容易耗尽codeCache。当发现codeCache有不够用的迹象(通过上一节提到的监控方式)时,可以通过启动参数来调整codeCache的大小。

-XX:ReservedCodeCacheSize=256M

总结

1.如果有一天你的系统在发布的时候突然间load上升、CPU上涨,几分钟之后恢复,可以考虑开启分层编译(-XX:+TieredCompilation)。应用中心可以查看系统的jit编译时间。(注意:先排除不是自己本次发布给弄起来的再考虑这点!!!!)

2.一旦开启分层编译就要考虑codeCache的大小,合理的调整codeCahe才能使分层编译达到目的,否则结果比较致命。(-XX:ReservedCodeCacheSize=256M)

注意:调整codeCache大小的时候注意PermSize大小,之前我的理解一直是错的,认为PermSize=永久代大小,实际PermSize=非堆内存大小

3.如果发布期间超时、load升高不能容忍的话,建议使用分层编译,可以容忍的话,一般两三分钟就会恢复

注意: 开启分层编译只是一个手段. 最好的办法, 还是排查代码问题, 是不是有热点代码/数据, 通过预加载, 低流量预热等方式从根本解决问题

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java类加载 的相关文章

随机推荐

  • JVM复习总结

    1 JVM运行时内存划分区域 方法区 线程公有 堆 线程公有 虚拟机栈 线程私有 本地方法栈 线程私有 程序计数器 线程私有 2 类加载器分类 启动类加载器 拓展类加载器 应用程序加载器 用户自定义加载器 3 类加载的步骤 加载 链接 验证
  • 嵌入式固件升级设计

    文章目录 IAP DFU和OTA的区别 DFU模式 DFU单区和双区 双区DFU设计 IAP DFU和OTA的区别 IAP In Application Programming IAP是用户自己的程序在运行过程中对User Flash的部分
  • pymongo保存dataframe格式的数据(insert_one, insert_many, 多线程保存)

    使用Pymongo保存数据的基本方法 增删改查 请参考 Python连接MongoDB 使用pymongo进行增删改查 文章目录 1 基本方法 逐行保存 2 insert many 批量保存 3 Threading 多线程保存数据 1 基本
  • [从零开始学DeepFaceLab-17]: 使用-命令行八大操作步骤-第7步:模型预测与生成合成图片 - 进阶 - 通过图形界面调参微调、精细合成图片

    目录 前言 第1章 如何进入可视化微调界面 第2章 窗口操作详解 2 1 操作图片选择
  • 软件测试职业规划

    软件测试职业规划 以下是转载内容 软件测试人员的发展误区 4 公司开发的产品专业性较强 软件测试人员需要有很强的专业知识 现在软件测试人员发展出现了一种测试管理者不愿意看到的景象 1 开发技术较强的软件测试人员转向了软件开发 非测试工具开发
  • spring boot 跨域问题(sessionid不一致 已解决)

    现象 Spring boot验证码接口与登录接口的sessionid不一致 解决方法 WebConfig中添加如下代码 Override public void addCorsMappings CorsRegistry registry r
  • 用ESP32玩转真彩屏

    很多人都说ESP32的出现是物联网开发者的福音 就是专为物联网应用而设计的 没错 我们都这样认为 ESP32不仅具有业内高水平的低功耗性能 而且它的高度集成特性 将天线开关 RF balun 功率放大器 接收低噪声放大器 滤波器 电源管理模
  • 微信开发相关:使用微信 JS-SDK 接口

    微信开发相关 使用微信 JS SDK 接口 准备工作 接口使用流程 公众号设置 前端向后端请求 ticket 后端向微信获取 token 后端根据 token 生成 ticket 根据 ticket 创建签名 前端创建配置信息 并注入验证
  • flutter Vertical viewport was given unbounded height

    问题描述 在Flutter开发中遇到 Vertical viewport was given unbounded height 问题出现的情况 这个问题主要是ListView builder出现的问题 如果是简单用的话 会出现这个问题的话
  • gcc/g++编译器的使用

    1 gcc编译器简介 gcc原名是GNU C Complier 支持C语言的编译链接 也支持C object c等语言的编译链接 根据 深入理解计算机系统 第三版 第1 2小节内容 gcc将一个源程序文件转换为最终的可执行程序需要经过预处理
  • IDEA 配置Maven国内源

    首先打开设置 在设置中搜索maven 然后跳转到这个页面 看到有一个User settings files这个项 Users xq m2 settings xml这个便是配置文件 修改这个文件即可 如果没有这个文件 可以新建一个settin
  • C++类模板的使用

    一 基本使用 通用类型用于成员变量 通用类型用于成员函数的参数 通用类型用于成员函数的返回值 获取成员变量 通用类型用于成员函数的代码中 代码 include
  • 【计算机视觉

    文章目录 一 检测相关 6篇 1 1 ALWOD Active Learning for Weakly Supervised Object Detection 1 2 mEBAL2 Database and Benchmark Image
  • C语言六种方法求素数(质数) 最全 输出2-100以内的所有素数 求1000以内的所有素数

    方法一 挨个遍历 从1 1000都试一次 通俗易懂的方法 include
  • 优秀程序员

    优秀程序员 拷贝型 新手型 学习型 实现型 架构型 1 拷贝型 拷贝型选手就是传说中的 代码拷贝员 了 他们对实现功能几乎没有思路 所作的事情就是从网上或是之前其他团队成员写的代码中拷贝出片段 然后放到项目中 如果运行项目出现了期望结果 则
  • 闲鱼无货源新玩法,从入门到精通,由浅入深教你怎么去做

    标题 闲鱼无货源新玩法 从入门到精通 由浅入深教你如何经营成功 随着电商的兴起 许多人开始利用平台经营自己的小生意 在这篇文章中 我们将聚焦于闲鱼平台 并分享关于如何从入门到精通运营成功的闲鱼无货源新玩法 以下是一些关键词 将帮助您更好地了
  • 7-1 二叉树的基本运算 (10 分)

    本习题为二叉树的基本运算练习 要求依次实现如下功能 1 输入一个使用 括号表示法 表示的二叉树 每个节点的数据为一个字符 请使用二叉链的存储方式构建二叉树B 2 使用中序遍历法遍历构建的二叉树 输出中序遍历的序列 3 输出该二叉树的高度 深
  • 【simulink】Three-PhaseV-I Measurement(三相电压电流测量)

    三相电压测量模块 MATLAB 三相电压电流测量模块怎么用 simulink Three PhaseV I Measurement 三相电压电流测量
  • 10X Genomics单细胞转录组测序

    一 单细胞及普通转录组比较 单细胞转录组测序 scRNA seq 在单个细胞水平上构建每个细胞的基因表达谱 反映细胞异质性 发现新的细胞类型 了解细胞表达调控机制 通过选取不同时间点的样本 再进行单细胞转录组测序 能够在单细胞水平获得基因
  • Java类加载

    类加载 执行 类加载 过程 对象的创建 类加载器 双亲委派 打破双亲委派 常见以tomcat和SPI为例 tomcat SPI 执行 解释执行 or 编译执行 关于编译 JIT编译器与解释器的工作模式 JIT编译器 分层编译 热点监测 热点