当前位置: 首页 > news >正文

《JVM学习笔记》字节码基础

前言

借用《深入理解Java虚拟机》中的一句话:代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,确实编程语言发展的一大步。

JVM提出的字节码数据格式的规范使得它不在仅仅服务于Java,而面向全语言,只要该语言能编译成符合JVM规范的字节码文件,就能运行在JVM之上进行跨平台运行。本篇文章意在学习class文件结构,了解这一平台无关性的基石。

一、本文概览

本文主要涵盖下图所示内容

在这里插入图片描述

二、JVM概述

1、JDK与JVM

JDK是Java的开发库,由Java开发出来的代码运行在JVM上。

  • Java是一门跨平台的语言,一次编译,可以运行在不同的操作系统上;
  • JVM是一款跨语言的平台,它只认字节码文件,任何一门语言只要能够编译成JVM所识别的字节码文件,都可以运行在JVM之上。

Java不是最强大的语言,但是JVM是最强大的虚拟机。

2、内存溢出?内存泄漏?

我们知道Java运行在JVM上,JVM提供了一套垃圾回收机制,不需要程序员手动进行内存释放,待内存到一定阈值便可自动进行垃圾回收工作,这大大提高了开发的效率。但是,垃圾收集也不是万能的,如果开发不当,也会导致内存的溢出或内存泄漏等场景。因此,了解JVM内部的存储结构、工作机制,是设计高扩展性应用和诊断运行时问题的基础,也是Java工程师提升的必备能力。

3、Java发展史

这里搜集的Java从2000年-2019年的历史事件

请添加图片描述

4、JVM的生命周期

  • 虚拟机的启动
    • Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现决定的
  • 虚拟机的运行
    • 正常的类加载、分配对象等等操作
  • 虚拟机的退出
    • 某线程调用Runtime类或System类的exit方法,或Runtime类的halt方法,并且Java安全管理器也允许这次exit或halt操作
    • 程序正常执行结束
    • 程序在执行过程中遇到了异常或错误而异常终止
    • 由于操作系统出现错误而导致Java虚拟机进程终止

5、JVM架构图

虚拟机:指以软件的方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中的完整计算机系统,是物理机的软件实现。常用的虚拟机有VMware,Visual Box,Java Virtual Machine(JVM)

请添加图片描述

三、字节码文件

字节码文件是跨平台的,它是由各个编译器编译成JVM所识别的二进制文件,目前不仅有Java可编译成字节码文件,还有其他众多语言,例如:Kotlin、Groovy、Scala等

1、概述

  • class文件里面是什么
    • 源代码经过编译器编译之后便会生成一个字节码文件,字节码是一种二进制文件,它的内容是JVM的执行,而不像C、C++经由编译器直接生成机器码
    • 随着Java平台的不断发展,在将来,class文件的内容也一定会作进一步的扩充,但是基本的格式和结构不会做重大调整
  • 生成class文件的编译器(前端编译器)
    • javac编译器:全量编译器
    • ECJ(Eclipse Compiler for Java)编译器:增量编译器
  • javac编译器的编译步骤
    • 词法解析
    • 语法解析
    • 语义解析
    • 生成字节码
  • 目前编译器的局限性
    • 前端编译器并不会直接设计编译优化等方面的技术,而是将这些具体优化细节移交给HotSpot的JIT编译器(后端编译器)负责

2、哪些类型对应有Class的对象

只要元素类型维度一样,那么所对应的Class对象就是一样的

  • class
    • 外部类
    • 内部类(成员内部类、静态内部类、局部内部类、匿名内部类)
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解
  • Primitive type:基本类型
  • void

3、字节码指令

Java虚拟机的指令由一个字节长度的、代表某种特定操作含义的操作码以及跟随其后的零至多个代表此操作所需参数的操作数所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。

请添加图片描述

四、class文件细节

0、class文件内容

CA FE BA BE 00 00 00 34 00 16 0A 00 04 00 12 09 00 03 00 13 07 00 14 07 00 15 01 00 03 6E 75 6D 01 00 01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 35 4C 63 6F 6D 2F 6D 61 72 6B 75 73 2F 6A 61 76 61 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 43 6C 61 73 73 46 69 6C 65 53 74 72 75 63 74 75 72 65 44 65 6D 6F 3B 01 00 03 61 64 64 01 00 03 28 29 49 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 1B 43 6C 61 73 73 46 69 6C 65 53 74 72 75 63 74 75 72 65 44 65 6D 6F 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 33 63 6F 6D 2F 6D 61 72 6B 75 73 2F 6A 61 76 61 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 43 6C 61 73 73 46 69 6C 65 53 74 72 75 63 74 75 72 65 44 65 6D 6F 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 00 00 38 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 0A 00 04 00 0B 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00 09 00 00 00 3D 00 03 00 01 00 00 00 0F 2A 2A B4 00 02 05 60 B5 00 02 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 0E 00 0A 00 0F 00 0B 00 00 00 0C 00 01 00 00 00 0F 00 0C 00 0D 00 00 00 01 00 10 00 00 00 02 00 11

1、概述

class文件的结构并不是一成不变的,随着Java虚拟机的不断发展,总是不可避免地会对class文件结构做出一些调整,但是其基本结构和框架是非常稳定的。

class文件的总体结构如下:

  • 魔数
  • class文件版本
  • 常量池
  • 访问标识(或标志)
  • 类索引,父类索引,接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

2、魔数

每个class文件的头4个字节被称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的class文件。标志性内容:CA FE BA BE

3、class文件版本

  • 第5~6个字节为次版本号(Minor Version)
  • 第7-8个字节为主版本号(Major Version)

请添加图片描述

4、常量池

常量池:存放所有常量

  • 常量池是class文件中内容最为丰富的区域之一。常量池对于class文件中的字段和方法解析也有着至关重要的作用。
  • 常量池:可以理解为class文件之中的资源仓库,它是class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用class文件空间最大的数据项目之一。
  • 常量池表项中,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

字面量和符号引用: 常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)

请添加图片描述

符号引用与直接引用的理解:

  • 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。

    • 被模块导出或者开放的包
    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符
    • 方法句柄和方法类型
    • 动态调用点和动态常量
  • 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存中了。

常量池的项目类型

请添加图片描述

5、访问标识

在常量池结束之后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:

请添加图片描述

6、类索引、父类索引、接口索引集合

类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据集合,class文件中有这三项数据来确定该类型的继承关系。

  • 类索引用于确定这个类的全限定名
  • 父类索引用于确定这个类的父类的全限定名
  • 接口索引用集合用来描述这个类实现了哪些接口

它们都是通过索引定位到常量池中的符号引用

7、字段表集合

字段表(field_info)用于描述接口类或者类中声明的变量。Java语言中的"字段"包括类级变量以及实例变量,但是不包括方法内部声明的局部变量。

请添加图片描述

8、方法表集合

方法表的结构和字段表一样,依次包括访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项。

请添加图片描述

对于方法里的代码,经过javac编译器编程字节码指令之后存在方法属性表集合中一个名为"Code"的属性里面。

9、属性表集合

  • 属性计数器
  • 属性表
    • ConstantValue属性
    • Deprecated属性
    • Code属性
    • InnerClasses属性
    • LineNumberTable属性
    • LocalVariableTable属性
    • Signature属性
    • SourceFile属性
    • 其他属性

五、Oracle官方的反解析工具

javap:做了解即可,可以用插件Jclasslib Bytecode Viewer

六、字节码指令

  • 一个指令,可以从局部变量表、常量池、堆中对象、方法调用、系统调用中等取得数据,这些数据(可能是值,可能是对象的引用)被压入操作数栈
  • 一个指令,也可以从操作数栈中取出一到多个值(pop多次),完成赋值、加减乘除、方法传参、系统调用等等操作

1、字节码指令集分类

  • 加载与存储指令
  • 算术指令
  • 类型转换指令
  • 对象的创建与访问指令
  • 方法调用与返回指令
  • 操作数栈管理指令
  • 控制转移指令
  • 异常处理指令
  • 同步控制指令

2、查看指令的介绍

记忆全部的指令不太现实,我们在查看的时候,能够去指定的地方查询了解即可。

查询链接:The Java® Virtual Machine Specification (oracle.com)
请添加图片描述

相关文章:

  • Java 学习 --SpringBoot 常用注解详解
  • 基于springboot网上书城系统
  • Java项目:JSP药店药品商城管理系统
  • app启动流程
  • 程序员的民宿情结
  • PD 重要监控指标详解
  • 数字集成电路(中)
  • 为什么Spring中的bean默认都是单例模式?
  • 【日常需求】一次使用EasyExcel而引发的问题与思考~
  • Docker 镜像拉取
  • Android 12 蓝牙打开
  • Linux常用基本命令详解(一)
  • 逻辑漏洞——业务逻辑问题
  • C++-vector的代码实现(超详细)
  • Linux之Platform设备驱动
  • 【css3】浏览器内核及其兼容性
  • 【Leetcode】104. 二叉树的最大深度
  • ES6系列(二)变量的解构赋值
  • Java 多线程编程之:notify 和 wait 用法
  • js学习笔记
  • React组件设计模式(一)
  • Sequelize 中文文档 v4 - Getting started - 入门
  • SQLServer之创建显式事务
  • Wamp集成环境 添加PHP的新版本
  • WinRAR存在严重的安全漏洞影响5亿用户
  • 包装类对象
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 力扣(LeetCode)357
  • 区块链技术特点之去中心化特性
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 山寨一个 Promise
  • mysql面试题分组并合并列
  • 国内开源镜像站点
  • 容器镜像
  • # C++之functional库用法整理
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • (1)SpringCloud 整合Python
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (pojstep1.1.2)2654(直叙式模拟)
  • (备忘)Java Map 遍历
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (九)c52学习之旅-定时器
  • (十一)图像的罗伯特梯度锐化
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • (转)大道至简,职场上做人做事做管理
  • ./configure、make、make install 命令
  • .NET Core 项目指定SDK版本
  • .net FrameWork简介,数组,枚举
  • .NET 回调、接口回调、 委托
  • .Net 垃圾回收机制原理(二)