字节码初体验-从HelloWorld开始

【字节码】基于javassist的第一个案例helloworld 关于 Javassist 的使用在完整的且强大的 API 下,确实还是蛮容易使用的。并且代码的使用上并不是很难理解。后续会陆续推出字节码编程的案例文章,逐步完善这部分技术知识栈的内容。最终尝试使用这样的技术知识完成一个案例级别的质量检测系统。也欢迎喜欢此类内容的小伙伴跟进学习。 阅读详情

不学习底层知识可能不会阻碍你成为一个称职的程序员,但也许会阻碍你成为一个优秀的程序员。我所理解的底层知识,是指编程或开发所依赖的平台(或者框架、工具)的知识。对于 Java 开发者来说,虚拟机、字节码就是其底层知识。

这篇文章我们以输出 “Hello, World” 来开始字节码之旅,如果之前没有怎么接触过字节码的话,这篇文章应该能够让你对字节码有一个最基本的认识

java 文件如何变成 .class 文件

新建一个 Hello.java 文件,源码如下:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, World");
    }
}

Java 从源文件到执行的过程如下图所示

JDK 工具 javac(java 编译器)帮我们完成了源文件编译成 JVM 可识别的 class 文件的工作。 在命令行中执行javac Hello.java,可以看到生成了 Hello.class 文件。用xxd 命令以 16 进制的方式查看这个 class 文件。

xxd Hello.class 
00000000: cafe babe 0000 0034 0022 0a00 0600 1409  .......4."......
00000010: 0015 0016 0800 170a 0018 0019 0700 1a07  ................
00000020: 001b 0100 063c 696e 6974 3e01 0003 2829  .....<init>...()
00000030: 5601 0004 436f 6465 0100 0f4c 696e 654e  V...Code...LineN
00000040: 756d 6265 7254 6162 6c65 0100 124c 6f63  umberTable...Loc
00000050: 616c 5661 7269 6162 6c65 5461 626c 6501  alVariableTable.
00000060: 0004 7468 6973 0100 074c 4865 6c6c 6f3b  ..this...LHello;

魔数 0xCAFEBABE


class 文件的头四个字节称为魔数(Magic Number),可以看到 class 的魔数为 0xCAFEBABE。很多文件都以魔数来进行文件类型的区分,比如 PDF 文件的魔数是%PDF-(16进制0x255044462D),png 文件的魔数是\x89PNG(0x89504E47)。文件格式的制定者可以自由的选择魔数值,只要魔数值还没有被广泛的采用过且不会引起混淆即可。

Java 早期开发者选用了这样一个浪漫气息的魔数,高司令有解释这一段 轶事。这个魔数值在 Java 还称为 Oak 语言的时候就已经确定下来了。

这个魔数是 JVM 识别 .class 文件的标志,虚拟机在加载类文件之前会先检查这四个字节,如果不是 0xCAFEBABE 则拒绝加载该文件,更多关于字节码格式的说明,我们会在后面的文章中慢慢介绍。

javap 详解

类文件是二进制块,想直接与它打交道比较艰难,但是很多情况下我们必须理解类文件。比如服务器上的接口出了 bug,重新打包部署以后问题并没有解决,为了找出原因你可能需要看一下部署以后的 class 文件究竟是不是我们想要的。还有一种情况跟你合作的开发商跑路了,只给你留下一堆编译过的代码,没有源代码,当出 bug 时我们需要研究这些类文件,看问题出在哪里。 好在 JDK 提供了专门用来分析类文件的工具:javap,用来方便的窥探 class 文件内部的细节。javap 有比较多的参数选项,其中-c -v -l -p -s是最常用的。

Usage: javap <options> <classes>
where possible options include:
  -help  --help  -?        Print this usage message
  -version                 Version information
  -v  -verbose             Print additional information
  -l                       Print line number and local variable tables
  -public                  Show only public classes and members
  -protected               Show protected/public classes and members
  -package                 Show package/protected/public classes
                           and members (default)
  -p  -private             Show all classes and members
  -c                       Disassemble the code
  -s                       Print internal type signatures
  -sysinfo                 Show system info (path, size, date, MD5 hash)
                           of class being processed
  -constants               Show final constants
  -classpath <path>        Specify where to find user class files
  -cp <path>               Specify where to find user class files
  -bootclasspath <path>    Override location of bootstrap class files

-c选项

最常用的选项是-c,可以对类进行反编译。执行javap -c Hello的输出结果如下

Compiled from "Hello.java"
public class Hello {
  public Hello();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3  // String Hello, World
       5: invokevirtual #4  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

上面代码前面的数字表示从方法开始算起的字节码偏移量

  • 3 ~ 7 行:可以看到虽然没有写 Hello 类的构造器函数,编译器会自动加上一个默认构造器函数
  • 5 行:aload_0 这个操作码是 aload_x 格式操作码中的一个。它们用来把对象引用加载到操作数栈。 x 表示正在被访问的局部变量数组的位置。在这里的 0 代表什么呢?我们知道非静态的函数都有第一个默认参数,那就是 this,这里的 aload_0 就是把 this 入栈
  • 6 行:invokespecial #1,invokespecial 指令调用实例初始化方法、私有方法、父类方法,#1 指的是常量池中的第一个,这里是方法引用 java/lang/Object."<init>":()V,也即构造器函数
  • 7 行:return,这个操作码属于 ireturn、lreturn、freturn、dreturn、areturn 和 return 操作码组中的一员,其中 i 表示 int,返回整数,同类的还有 l 表示 long,f 表示 float,d 表示 double,a 表示 对象引用。没有前缀类型字母的 return 表示返回 void

到此为止,默认构造器函数就讲完了,接下来,我们来看 9 ~ 14 行的 main 函数

  • 11 行:getstatic #2,getstatic 获取指定类的静态域,并将其值压入栈顶,#2 代表常量池中的第 2 个,这里表示的是java/lang/System.out:Ljava/io/PrintStream;,其实就是java.lang.System 类的静态变量 out(类型是 PrintStream)
  • 12 行:ldc #3、,ldc 用来将常量从运行时常量池压栈到操作数栈,#3 代表常量池的第三个(字符串 Hello, World)
  • 13 行:invokevirtual #4,invokevirutal 指令调用一个对象的实例方法,#4 表示 PrintStream.println(String) 函数引用,并把栈顶两个元素出栈

-p 选项

默认情况下,javap 会显示访问权限为 public、protected 和默认(包级 protected)级别的方法,加上 -p 选项以后可以显示 private 方法和字段

-v 选项

javap 加上 -v 参数的输出更多详细的信息,比如栈大小、方法参数的个数。

public Hello();
    stack=1, locals=1, args_size=1
        
public static void main(java.lang.String[]);
    stack=2, locals=1, args_size=1

为什么Hello()和main()的args_size都等于1呢?明明 Hello 的构造器函数没有参数的呀? 对于非静态函数,this对象会作为函数的隐式第一个参数,所以Hello()的args_size=1 对于静态main函数,不需要this对象,它的参数就是String[] args这个数组,也等于1

-s选项

javap 还有一个好用的选项 -s,可以输出签名的类型描述符。我们可以看下 Hello.java 所有的方法签名

javap -s Hello  
Compiled from "Hello.java"
public class Hello {
  public Hello();
    descriptor: ()V
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
}

可以看到 main 函数的方法签名是 ([Ljava/lang/String;)V。JVM 内部使用方法签名与我们日常阅读的方法签名不太一样,但是后面会频繁遇到,主要分为两部分字段描述符和方法描述符。

字段描述符(Field Descriptor),是一个表示类、实例或局部变量的语法符号,它的表示形式是紧凑的,比如 int 是用 I 表示的。完整的类型描述符如下表


方法描述符(Method Descriptor)
表示一个方法所需参数和返回值信息,表示形式为( ParameterDescriptor* ) ReturnDescriptor。 ParameterDescriptor 表示参数类型,ReturnDescriptor表示返回值信息,当没有返回值时用V表示。比如方法Object foo(int i, double d, Thread t)的描述符为(IDLjava/lang/Thread;)Ljava/lang/Object;

小结

这篇文章讲解了一个输出 “Hello, World” 的字节码的细节,一起来回顾一下要点:

  • 第一,class 文件的魔数是具有浪漫气息的 0xCAFEBABE;
  • 第二,我们讲解了字节码分析的利器 javap 的各个参数详细的用法。第三,讲解了字段描述符与方法描述符在 JVM 层面的表示规则,方便我们后面文章的理解。
java反编译工具,能将.class文件转换成.java文件 该工具小巧,功能强大,能100%将class文件反编译为java文件。 立即下载

相关推荐

将class文件转换成java文件

可以很方便的将.jar文件反转成java文件。

java编译成class文件方法

安装完成后,记得配置环境变量。我们将JDK的安装目录下的bin文件所在路径配置到环境变量的path变量中。注意java文件名应与代码内定义的类名一致。比如我们有Hack.java文件。首先需安装jdk,可从官网下载。怎么编译成class文件呢。发现生成class文件。

wutiangui的博客 1935

ANSYS Maxwell:从RMxprt到3D轴向磁通电机的无缝建模指南

本文详细介绍了如何利用ANSYS RMxprt作为高效起点,快速构建3D轴向磁通电机的有限元分析模型。通过RMxprt内置模板输入关键参数并完成磁路法计算后,可一键生成包含几何、材料、绕组及边界条件的即用型Maxwell 3D模型,极大简化了复杂三维结构的建模流程,使工程师能专注于性能优化与仿真分析。

weixin_29281941的博客 553

java文件如何转换为class文件(HelloWorld举例)

Java运行如何将写好的Java文件转化为class文件,以输出Helloword为例。

weixin_52147015的博客 1万+

Zemax实战:20×超高清变焦镜头设计全流程解析(从架构到制造)

本文详细解析了使用Zemax软件设计一款20倍变焦比、恒定F2.8光圈8K超高清镜头的全流程。从应对恒定光圈、宽光谱与8K解析力三大挑战的初始架构规划,到构建多重组态模型、分七阶段进行像差优化与公差分析,最终跨越制造鸿沟,为广播级影视与高精度工业检测应用提供了实战指南。

sun99的博客 551

【CATIA的二次开发38】对象Selection交互选择部分方法介绍01

在CATIA V5的VBA开发中,Selection对象是用户交互的核心组件,用于管理用户在图形区域或特征树中的选择操作。Selection 对象是 CATIA VBA 中的中央交互枢纽,充当用户界面与程序逻辑之间的桥梁。它代表当前在图形区域或特征树中被选中的元素集合,是自动化操作的基础。

学习共勉:不积跬步,无以至千里; 不积小流,无以成江海。 1339

Java文件是怎么编译成Class文件的

其实就是我们前端的编译过程,是通过javac(编译器)把java文件变成.class字节码文件。 如上图所示,这里给出一个javac 源码中的 JavaCompiler.java 按照上图,逐步分析读取源代码,一个字节一个字节读取出来,找到这些词法中的语句比如:访问修饰符、类和类名、条件语句、循环结构、基础的语法等等。结论:是将源代码的字符流转变为标记(Token)集合的过程,单个字符是程序编写时的最小元素,但标记才是编译时的最小元素。关键字、变量名、字面量、运算符都可以作为标记,如下代码: 这句代码中就

想着百万年薪努力的小赵 1万+

机器学习教程 之 半监督学习 Co-training 协同训练 (论文、算法、数据集、代码)

这篇博客介绍的是一篇用于半监督分类问题的方法: 协同训练 Co-training, A. Blum and T. Mitchell, “Combining labeled and unlabeled data with co-training,” in Proc. of the 11th Annual Conf. on Computational Learning Theory, 1998.,这算是半监督领域里面始祖级的论文了,是1998年两位CMU大佬提出的方法。 之前还写过一篇关于Tri-trainin

冯良骏 的 博客 1万+

JVM真香系列:.java文件到.class文件

关注“Java后端技术全栈”回复“000”获取大量电子书认识JVM什么是JVMJVM 全称 Java Virtual Machine,也就是我们耳熟能详的 Java 虚拟机。它能识别 ....

田维常 781

三. zemax光学设计流程+案例

文章目录zemax 基本操作:1. 建立光学基本模型2. 像质评价3. 优化(重点)4. 公差分析简单镜头设计 zemax 基本操作: 1. 建立光学基本模型 2. 像质评价 3. 优化(重点) 对系统设定的约束条件或目标值 统称为操作数。 操作数: 光学特性参数(焦距EFFL,近轴放大率PMAG,入瞳位置EMPP) 像差参数(球差SPHA,彗差COMA,像散ASTI) 边界条件(中心厚度值CTVA,边缘厚度值ETAV) 优化的主要步骤: 设置评价函数和优化操作数 一般小像差系统采用波像差 构建

Taiiiii的博客 8052

基于 FPGA Vivado 信号发生器设计(附源工程)

今天给大侠带来基于 FPGA Vivado 信号发生器设计,开发板实现使用的是Digilent basys 3,如有想要入手basys3开发板的,可以联系牛总:18511371833。话不多说,上货。 本篇掌握基于 FPGA Vivado 信号发生器设计(附源工程),掌握基于添加文件和IP的Vivado工程设计流程,掌握基于Tcl的Vivado工程设计流程,学习信号发生器的基本组成结构。获取本篇相关源工程代码,可在公众号内回复“信号发生器设计源工程”。 设计原理 信号发生器...

7990

怎么把java文件编译为class文件

java文件轻松编译为class文件 1.需要有配置好的java的环境变量 2.在你的jdk目录下的bin目录下打开cmd 2.然后找到你要编译的java文件的路径,例如我要编译的java文件在:E:IntelliJ IDEAWorkplaceSpringJdbcTestsrcBookShopMain.java 3.使用命令为javac E:IntelliJ IDEAWorkplaceSpringJdbcTestsrcBookShopMain.java就大功告成了,就可以找到相应的class文件 ..

m0_67401761的博客 8532

[ vulhub漏洞复现篇 ] Drupal XSS漏洞 (CVE-2019-6341)

[ vulhub漏洞复现篇 ] Drupal XSS漏洞 (CVE-2019-6341) Drupal是使用PHP语言编写的开源内容管理框架(CMF),它由内容管理系统(CMS)和PHP开发框架(Framework)共同构成,在GPL2.0及更新协议下发布。Drupal在某些情况下,通过文件模块或者子系统上传恶意文件触发XSS漏洞。

qq_51577576的博客 2208

HelloWorld 将字节码剖析到底

★二三十岁的人,醉的好坏不分,哭的大声,记录着青春~——《二三十》 李荣浩”内容摘要1. new指令温故2. 字节码、机器码简述3. HelloWorld字节码剖析1.new指令温故实...

weixin_46080041的博客 357

YOLO26涨点改进 | 检测头Head创新改进篇 | 利用DynamicConv高效动态卷积改进YOLO26检测头,DyHead通过增强检测头操作,轻量化改进、提高模型目标检测的精度

YOLO26作为新一代轻量化目标检测模型的代表,凭借精简的Backbone架构、高效的特征融合 Neck 设计,在实时检测场景(如嵌入式设备、边缘计算)中展现出优异的性能,成为工业落地与科研入门的优选模型。但在实际应用中,YOLO26原生检测头仍存在核心瓶颈:采用固定参数卷积核,无法根据输入特征的目标尺度、纹理差异动态调整卷积权重,导致多尺度目标适配不足、复杂背景下特征利用率低,且传统改进方案往往难以平衡“精度提升”与“轻量化”,要么精度提升有限,要么大幅增加参数量、拖累推理速度。

qq_36130719的博客 370

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

从字节码角度仔细剖析一个HelloWorld程序

1. 变身的HelloWorld 2. 初识字节码 3. 解析HelloWorld字节码 4. 结束语 变身的HelloWorld 相信学过Java的朋友都知道HelloWorld是什么,当年第一次成功运行HelloWorld程序的激动似乎还宛如昨天的事,历历在目。没错,今天就讲一个HelloWorld的Java程序~ public class HelloWorld { public s...

易水南风的专栏 1182

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

从字节码层面看“HelloWorld”

一、HelloWorld 字节码生成   众所周知,Java 程序是在 JVM 上运行的,不过 JVM 运行的其实不是 Java 语言本身,而是 Java 程序编译成的字节码文件。可能一开始 JVM 是为 Java 语言服务的,不过随着编译技术和 JVM 自身的不断发展和成熟,JVM 已经不仅仅只运行 Java 程序。任何能编译成为符合 JVM 字节码规范的语言都可以在 JVM 上运行...

ankuiba5737的博客 251

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

硬核手写字节码实现HelloWorld

这是一篇硬(闲的)核(蛋疼)的文章,我们来通过手写字节码的方式,来完成一个Helloworld。起因很简单,最近在看《深入理解JVM字节码》,尝试了解读class文件,但是总觉得不过瘾,...

麒思妙想 635

java中字节码_Java从字节码层面看“HelloWorld”

一、HelloWorld 字节码生成众所周知,Java 程序是在 JVM 上运行的,不过 JVM 运行的其实不是 Java 语言本身,而是 Java 程序编译成的字节码文件。可能一开始 JVM 是为 Java 语言服务的,不过随着编译技术和 JVM 自身的不断发展和成熟,JVM 已经不仅仅只运行 Java 程序。任何能编译成为符合 JVM 字节码规范的语言都可以在 JVM 上运行,比较常见的 Sc...

weixin_29623163的博客 142

字节码编程,Javassist篇一《基于javassist的第一个案例helloworld》

作者:小傅哥 博客:https://bugstack.cn 沉淀、分享、成长,让自己和他人都能有所收获! 目录 文章目录目录一、前言二、开发环境三、案例目标四、技术实现五、测试结果1. 使用Javassist生成的类2. 输出的测试结果六、总结 一、前言 在字节码编程方面有三个比较常见的框架;ASM、byte-buddy、Javassist,他们都可以对这字节码进行操作,只是操作方式和控制粒...

bugstack虫洞栈 2352

解读HelloWorld字节码

上一篇文章我们拿到了HelloWorld的字节码,这回我们来搞他,我稍微整理一下,我们一起来研究。 字节码的组成 1.首先是类主体的定义部分,包括了我们的类版本号,访问修饰符 public class HelloWorld minor version: 0//次版本号 major version: 52//主版本号52对应jdk1.8 flags: ACC_PUBLIC, ACC_SU...

敏叔 752

字节码看HelloWorld

一、HelloWorld 字节码生成   众所周知,Java 程序是在 JVM 上运行的,不过 JVM 运行的其实不是 Java 语言本身,而是 Java 程序编译成的字节码文件。可能一开始 JVM 是为 Java 语言服务的,不过随着编译技术和 JVM 自身的不断发展和成熟,JVM 已经不仅仅只运行 Java 程序。任何能编译成为符合 JVM 字节码规范的语言都可以在 JVM 上运行,比较常见

你若盛开~ 861

学java占几个字节_硬核手写字节码实现HelloWorld

这是一篇硬(闲的)核(蛋疼)的文章,我们来通过手写字节码的方式,来完成一个Helloworld。起因很简单,最近在看《深入理解JVM字节码》,尝试了解读class文件,但是总觉得不过瘾,那么我们来试试手写一个class。工具所谓工欲善其事必先利其器,我们先说一下工具,写字节码我用了sublime,它可以直接将文件打开成16进制查看, 并且直接进行编辑,非常方便,并且sublime允许我们...

weixin_39969953的博客 308

字节码编程,Byte-buddy篇一《基于Byte Buddy语法创建的第一个HelloWorld》

作者:小傅哥 博客:https://bugstack.cn 沉淀、分享、成长,让自己和他人都能有所收获! 一、前言 相对于小傅哥之前编写的字节码编程; ASM、Javassist 系列,Byte Buddy 玩法上更加高级,你可以完全不需要了解一个类和方法块是如何通过 指令码 LDC、LOAD、STORE、IRETURN… 生成出来的。就像它的官网介绍; Byte Buddy 是一个代码生成和操作库,用于在 Java 应用程序运行时创建和修改 Java 类,而无需编译器的帮助。除了 Java 类库附带.

bugstack虫洞栈 3387

程序员进阶系列:你真的懂 HelloWorld 吗?

作为入了门的 Java 程序员,相信在脑海中都能够秒写出 HelloWorld.java,都知道编译成 HelloWorld.class,然后就可以跨平台执行了。常言道:知人知面不知心。...

一猿小讲 5322
上一篇: 字节码原理浅析 —— 基于栈的执行引擎
下一篇: ElasticSearch系列-简介与安装
yue_stack
博客等级 码龄3年 6粉丝 43原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值