得心应手应对 OOM 的疑难杂症

news2024/12/26 10:44:43

Java全能学习+面试指南:https://www.javaxiaobear.cn/

前面我们提到,类的初始化发生在类加载阶段,那对象都有哪些创建方式呢?除了我们常用的 new,还有下面这些方式:

  • 使用 Class 的 newInstance 方法。
  • 使用 Constructor 类的 newInstance 方法。
  • 反序列化。
  • 使用 Object 的 clone 方法。

其中,后面两种方式没有调用到构造函数。

当虚拟机遇到一条 new 指令时,首先会检查这个指令的参数能否在常量池中定位一个符号引用。然后检查这个符号引用的类字节码是否加载、解析和初始化。如果没有,将执行对应的类加载过程。

拿我们上面的代码来说,执行 A 代码,在调用 private B b = new B() 时,就会触发 B 类的加载。

让我们结合上图回顾一下前面章节的内容。A 和 B 会被加载到元空间的方法区,进入 main 方法后,将会交给执行引擎执行。这个执行过程是在栈上完成的,其中有几个重要的区域,包括虚拟机栈、程序计数器等。接下来我们详细看一下虚拟机栈上的执行过程。

查看字节码

命令行查看字节码

使用下面的命令编译源代码 A.java。如果你用的是 Idea,可以直接将参数追加在 VM options 里面。

javac -g:lines -g:vars A.java

这将强制生成 LineNumberTable 和 LocalVariableTable。

然后使用 javap 命令查看 A 和 B 的字节码。

javap -p -v A
javap -p -v B

这个命令,不仅会输出行号、本地变量表信息、反编译汇编代码,还会输出当前类用到的常量池等信息。由于内容很长,这里就不具体展示了,你可以使用上面的命令实际操作一下就可以了。

注意 javap 中的如下字样。

<1>

1: invokespecial #1   // Method java/lang/Object."<init>":()V

可以看到对象的初始化,首先是调用了 Object 类的初始化方法。注意这里是 <init> 而不是 <cinit>。

<2>

#2 = Fieldref           #6.#27         // B.a:I

它其实直接拼接了 #13 和 #14 的内容。

#6 = Class             #29           // B
#27 = NameAndType       #8:#9         // a:I
...
#8 = Utf8               a
#9 = Utf8               I

<3>

你会注意到 :I 这样特殊的字符。它们也是有意义的,如果你经常使用 jmap 这种命令,应该不会陌生。大体包括:

  • B 基本类型 byte
  • C 基本类型 char
  • D 基本类型 double
  • F 基本类型 float
  • I 基本类型 int
  • J 基本类型 long
  • S 基本类型 short
  • Z 基本类型 boolean
  • V 特殊类型 void
  • L 对象类型,以分号结尾,如 Ljava/lang/Object;
  • [Ljava/lang/String; 数组类型,每一位使用一个前置的"["字符来描述

我们注意到 code 区域,有非常多的二进制指令。如果你接触过汇编语言,会发现它们之间其实有一定的相似性。但这些二进制指令,并不是操作系统能够认识的,它们是提供给 JVM 运行的源材料。

可视化查看字节码

接下来,我们就可以使用更加直观的工具 jclasslib,来查看字节码中的具体内容了。

我们以 B.class 文件为例,来查看它的内容。

<1>

首先,我们能够看到 Constant Pool(常量池),这些内容,就存放于我们的 Metaspace 区域,属于非堆。

常量池包含 .class 文件常量池、运行时常量池、String 常量池等部分,大多是一些静态内容。

<2>

接下来,可以看到两个默认的 <init> 和 <cinit> 方法。以下截图是 test 方法的 code 区域,比命令行版的更加直观。

<3>

继续往下看,我们看到了 LocalVariableTable 的三个变量。其中,slot 0 指向的是 this 关键字。该属性的作用是描述帧栈中局部变量与源码中定义的变量之间的关系。如果没有这些信息,那么在 IDE 中引用这个方法时,将无法获取到方法名,取而代之的则是 arg0 这样的变量名。

本地变量表的 slot 是可以复用的。注意一个有意思的地方,index 的最大值为 3,证明了本地变量表同时最多能够存放 4 个变量。

另外,我们观察到还有 LineNumberTable 等选项。该属性的作用是描述源码行号与字节码行号(字节码偏移量)之间的对应关系,有了这些信息,在 debug 时,就能够获取到发生异常的源代码行号。

test 函数执行过程

Code 区域介绍

test 函数同时使用了成员变量 a、静态变量 C,以及输入参数 num。我们此时说的函数执行,内存其实就是在虚拟机栈上分配的。下面这些内容,就是 test 方法的字节码。

public long test(long);
   descriptor: (J)J
   flags: ACC_PUBLIC
   Code:
     stack=4, locals=5, args_size=2
        0: aload_0
        1: getfield      #2                  // Field a:I
        4: i2l
        5: lload_1
        6: ladd
        7: getstatic     #3                  // Field C:J
       10: ladd
       11: lstore_3
       12: lload_3
       13: lreturn
     LineNumberTable:
       line 13: 0
       line 14: 12
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      14     0  this   LB;
           0      14     1   num   J
          12       2     3   ret   J

我们介绍一下比较重要的 3 三个数值。
<1>

首先,注意 stack 字样,它此时的数值为 4,表明了 test 方法的最大操作数栈深度为 4。JVM 运行时,会根据这个数值,来分配栈帧中操作栈的深度。

<2>

相对应的,locals 变量存储了局部变量的存储空间。它的单位是 Slot(槽),可以被重用。其中存放的内容,包括:

  • this
  • 方法参数
  • 异常处理器的参数
  • 方法体中定义的局部变量

<3>

args_size 就比较好理解。它指的是方法的参数个数,因为每个方法都有一个隐藏参数 this,所以这里的数字是 2。

字节码执行过程

我们稍微回顾一下 JVM 运行时的相关内容。main 线程会拥有两个主要的运行时区域:Java 虚拟机栈和程序计数器。其中,虚拟机栈中的每一项内容叫作栈帧,栈帧中包含四项内容:局部变量报表、操作数栈、动态链接和完成出口。

我们的字节码指令,就是靠操作这些数据结构运行的。下面我们看一下具体的字节码指令。

(1)0: aload_0

把第 1 个引用型局部变量推到操作数栈,这里的意思是把 this 装载到了操作数栈中。

对于 static 方法,aload_0 表示对方法的第一个参数的操作。

(2)1: getfield      #2

将栈顶的指定的对象的第 2 个实例域(Field)的值,压入栈顶。#2 就是指的我们的成员变量 a。

#2 = Fieldref           #6.#27         // B.a:I
...
#6 = Class             #29           // B
#27 = NameAndType       #8:#9         // a:I

(3)i2l

将栈顶 int 类型的数据转化为 long 类型,这里就涉及我们的隐式类型转换了。图中的信息没有变动,不再详解介绍。

(4)lload_1

将第一个局部变量入栈。也就是我们的参数 num。这里的 l 表示 long,同样用于局部变量装载。你会看到这个位置的局部变量,一开始就已经有值了。

(5)ladd

把栈顶两个 long 型数值出栈后相加,并将结果入栈。

(6)getstatic #3

根据偏移获取静态属性的值,并把这个值 push 到操作数栈上。

(7)ladd

再次执行 ladd。

(8)lstore_3

把栈顶 long 型数值存入第 4 个局部变量。

还记得我们上面的图么?slot 为 4,索引为 3 的就是 ret 变量。

(9)lload_3

正好与上面相反。上面是变量存入,我们现在要做的,就是把这个变量 ret,压入虚拟机栈中。

(10)lreturn

从当前方法返回 long。

到此为止,我们的函数就完成了相加动作,执行成功了。JVM 为我们提供了非常丰富的字节码指令。详细的字节码指令列表,可以参考以下网址:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html

注意点

注意上面的第 8 步,我们首先把变量存放到了变量报表,然后又拿出这个值,把它入栈。为什么会有这种多此一举的操作?原因就在于我们定义了 ret 变量。JVM 不知道后面还会不会用到这个变量,所以只好傻瓜式的顺序执行。

为了看到这些差异。大家可以把我们的程序稍微改动一下,直接返回这个值。

public long test(long num) {
       return this.a + num + C;
}

再次看下,对应的字节码指令是不是简单了很多?

0: aload_0
1: getfield     #2                 // Field a:I
4: i2l
5: lload_1
6: ladd
7: getstatic     #3                 // Field C:J
10: ladd
11: lreturn

那我们以后编写程序时,是不是要尽量少的定义成员变量?

这是没有必要的。栈的操作复杂度是 O(1),对我们的程序性能几乎没有影响。平常的代码编写,还是以可读性作为首要任务。

小结

我们学会了使用 javap 和 jclasslib 两个工具。平常工作中,掌握第一个就够了,后者主要为我们提供更加直观的展示。

我们从实际分析一段代码开始,详细介绍了几个字节码指令对程序计数器、局部变量表、操作数栈等内容的影响,初步接触了 Java 的字节码文件格式。

希望你能够建立起一个运行时的脉络,在看到相关的 opcode 时,能够举一反三的思考背后对这些数据结构的操作。这样理解的字节码指令,根本不会忘。

你还可以尝试着对 A 类的代码进行分析,我们这里先留下一个悬念。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ssprompt:一个LLM Prompt分发管理工具

阅读顺序 &#x1f31f;前言&#x1f514;ssprompt介绍命令介绍Metafile介绍版本依赖规则 &#x1f30a; PromptHubGitHub Token &#x1f680; Quick Install系统依赖pip安装Linux, macOS, Windows (WSL)Windows (Powershell) &#x1f6a9; Roadmap&#x1f30f; 项目交流讨论…

智汇云舟出席全球制造业峰会并作主题分享

9月1日-5日&#xff0c;由中华人民共和国商务部、中华人民共和国国家发展和改革委员会、中华人民共和国科学技术部、中国国际贸易促进委员会、辽宁省人民政府共同主办的全球制造业峰会暨第二十一届中国&#xff08;沈阳&#xff09;国际装备制造业博览会在沈阳国际会展中心召开…

身份证二要素核验-实名认证API:数据隐私的守护先锋

前言 在当今数字化世界中&#xff0c;数据隐私和安全问题变得尤为重要。随着互联网的普及&#xff0c;个人信息的泄露已经成为潜在的威胁。为了应对这一挑战&#xff0c;身份证二要素核验-实名认证API成为了数据隐私的守护先锋&#xff0c;提供了一种强大的工具来确保个人信息…

前端面试的话术集锦第 8 篇:高频考点(JS性能优化 性能优化琐碎事)

这是记录前端面试的话术集锦第八篇博文——高频考点(JS性能优化 & 性能优化琐碎事),我会不断更新该博文。❗❗❗ 1. 从V8中看JS性能优化 注意:该知识点属于性能优化领域。 1.1 测试性能⼯具 Chrome已经提供了⼀个⼤⽽全的性能测试⼯具Audits。 点我们点击Audits后,可…

分享一个基于SpringBoot+Vue的招生宣传管理系统源码 招生计划管理系统代码

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

day-07 I/O 复用(select)- 多种 I/O 函数 - 多播和广播

一.I/O复用 &#xff08;一&#xff09;基于I/O复用的服务器端 1.多进程服务器 每次服务都需要创建一个进程&#xff0c;需要大量的运算和内存空间 2.复用 只需创建一个进程。 3.复用技术在服务器端的应用 &#xff08;二&#xff09;select函数实现服务器端 &#xff08;…

二维码智慧门牌管理系统:让城市管理更智能、便捷

文章目录 前言一、二维码智慧门牌管理系统的特点二、数据集约化与规范化三、管理智能化与长效化四、标识规范化与易维护五、服务多元化与便捷化 前言 随着城市化进程的加速&#xff0c;城市管理面临着越来越多的挑战。为了解决地名地址管理交织错综、地名地址支撑政府管理成效…

华为云云服务器评测| 之兼容性测试

文章目录 前言创建文本文件编写脚本赋予脚本执行权限及脚本运行结果解析结果说明 : 总结 前言 兼容性测试作为软件开发和系统维护中的关键环节&#xff0c;对于确保系统的可靠性和稳定性至关重要。随着云计算的兴起&#xff0c;越来越多的企业选择将应用程序和服务迁移到云平台…

集合结构体系

集合体系结构 collection单链集合&#xff1a;是单列集合的祖宗接口&#xff0c;他的功能是所有单列集合可以继承使用的 创建collection对象 Collection<String> collnew ArrayList<>();//学习阶段才创建的collection中的arraylist方法名称说明boolean add()集合中…

Unity 之预制体(Prefab)的解释以及用法

文章目录 详细解释代码举例 详细解释 Unity中的预制体&#xff08;Prefab&#xff09;是一种重要的资源类型&#xff0c;用于创建和管理游戏对象&#xff08;GameObject&#xff09;。预制体是一种可重用的模板&#xff0c;可以在游戏场景中多次实例化&#xff0c;使开发人员能…

【STM32学习】——SPI通信协议SPI时序W25Q64存储芯片软件SPI读写

目录 前言 一、SPI通信协议 1.概述​ 2.硬件电路 3.移位示意图 二、SPI时序 1.时序基本单元 2.完整时序波形 三、W25Q64存储芯片 1.芯片简介 2.硬件电路&引脚定义 3.芯片框图 4.Flash操作注意事项 四、软件SPI读写W25Q64 五、SPI通信外设 总结 前言 声明&…

城市公共交通概念新解

“城市&#xff0c;让生活更美好”&#xff0c;城市是公众多元需求最为集中体现之所在&#xff0c;亦是获得满足之场所。同时&#xff0c;它也是科技创新、文化融合、经济发展的基座与舞台。城市交通作为城市运行的重要系统&#xff0c;相当于城市有机体的血脉和经络&#xff0…

命令行git联网失败,但是实际可以联网

最近下载代码的时候发现总是告诉我连不上github的网页&#xff0c;但是我自己通过浏览器又可以上网&#xff0c;找了半天发现这个方法可以。 记录下这个代理 打开git bash 执行以下命令&#xff1a; git config --global http.proxy http://127.0.0.1:7890 git config --glob…

【运维日常】infinibang网络架构,容器间跨机器不同网段通信

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

【JavaScript】在指定dom元素前面创建标签元素

一、基础操作过程 要在指定的DOM元素前面创建标签元素&#xff0c;有以下步骤&#xff1a; 获取指定的DOM元素&#xff1a;使用document.querySelector()或document.getElementById()等方法来获取指定的DOM元素。 const targetElement document.querySelector(#targetElement…

基于HBuilder X平台下的 驾校报名考试管理系统 uniapp 微信小程序3n9o5

本课题研究的是基于HBuilder X系统平台下的驾校管理系统&#xff0c;开发这款驾校管理系统主要是为了帮助学员可以不用约束时间与地点进行查看教练信息、考场信息等内容。本文详细讲述了驾校管理系统的界面设计及使用&#xff0c;主要包括界面的实现、控件的使用、界面的布局和…

Qt应用开发(基础篇)——复选按钮 QCheckBox 单选按钮 QRadioButton

一、前言 QCheckBox类与QRadioButton类继承于QAbstractButton&#xff0c;QCheckBox是一个带有文本标签的复选框&#xff0c;QRadioButton是一个带有文本标签的单选按钮。 按钮基类 QAbstractButton QCheckBox QCheckBox复选框是一个很常用的控件&#xff0c;拥有开关(选中和未…

微信小程序开发前准备

文章目录 一、注册微信小程序开发账号&#xff08;一&#xff09;访问微信公众号平台官网&#xff08;二&#xff09;进入注册页面 二、获取微信小程序AppID三、安装微信开发者工具四、创建微信小程序项目 在正式开发微信小程序前&#xff0c;需要先进行开发前的准备工作&#…

rhcsa学习3 文件

文件系统权限 有三种权限类别可应用&#xff1a;读取、写入和执行 查看文件和目录的权限及所有权 ls -l 显示有关权限和所有权的详细信息 长列表的第一个字符表示文件类型 - 表示常规文件、d 目录、l 软链接、其它字符代表硬件设备&#xff08;b和c&#xff09;或其他具有特殊用…

内推与面试:内部推荐如何影响面试结果

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…