破解class文件的第一步:深入理解JAVA Class文件
摘要: java定义了一套与操作系统,硬件无关的字节码格式,这个字节码就是用java class文件来表示的,java class文件内部定义了虚拟机可以识别的字节码格式,这个格式是平台无关性的。
java语言是跨平台的,所谓一次编写,到处运行。之所以是跨平台的,就是java定义了一套与操作系统,硬件无关的字节码格式,这个字节码就是用java class文件来表示的,java class文件内部定义了虚拟机可以识别的字节码格式,这个格式是平台无关性的,在linux系统或者在windows系统上都是一致的。这个就好比html文件,我们定义好规范,这个系统只要去按照规范显示出来里面的内容就好了。
一.JVM的语言无关性
JVM是干什么用的?
运行java的啊,难不成是运行python的?
这句话是对的,但不完整,JVM并不是只能运行java程序。
事实上,JVM上运行的本身也不是java文件,而是class文件。
而能够编译转化为class文件的,并不只有java一种。
这就是JVM的语言无关性。
至于能不能运行python,取决于是否有一个能将python转成class文件的工具。
当然这样做没有太多的意义,毕竟python也有其运行环境,且在某种意义上,比java更强大,核心类库更完善。
各种语言也有各自的平台,所以没有必要强制编译。
但掌握class文件还是很有意义的。
作为一个程序员,你是否有过或者曾经有过创建一门语言的奢望?最好还是用汉语开发。
但现实,或者大学里的某个导师,却给你兜头一盆冷水。
先花个三五年研究汇编,再考虑实现这些。
三五年,黄花菜都凉了。
现在,有了JVM,似乎看到了一点希望的曙光。
二.class文件的本质
要实现之前的设想,或者说,想开发一个编译工具。首先要做的,就是要解构class文件本身。
无论如何得来,class文件的本质都是一组以 8 位字节为基础单位的2进制流。
记住,是2进制。
为了证明这一点,我们还是要用到一些工具。比如,Sublime。
它并不是一个直接查看2进制的工具,而是16 进制的编辑器(2进制和16进制可以无缝切换)。
这里面似乎还有python的事情哦。使用时,直接点击sublime_text.exe文件即可。
然后选择class文件,打开,如下图的样子。
看的人眼花对不对?这都什么玩意!
前文说了,2进制,不,这就是16进制啊。
如果你不想去看16进制,也可以使用javap,直接去查看字节码指令(详细内容见前文《一段java代码是如何执行的》)。
如果你也不想打开命令行,还有一个叫jclasslib的工具,可提供图形化界面,它还有适用于idea的插件。
但它不是重点,暂且忽略。
三.class文件结构揭秘
class文件格式中只有两种数据类型,无符号数和表。
其中,无符号数包含所有的基础数据类型和字符串,索引引用等,根据字节长度又可以分为u1,u2,u4,u8,分别代表无符号数的长度为1,2,4,8。
而表,即对象类型。
接下来,以sublime文件解析的内容为蓝本,按顺序说说的class文件的构成。
(1)class文件的头四个字节被称为魔数,它的作用是确定这个文件是否为一个能被虚拟机接受的 Class 文件。
如,上文中魔数的值为:
它代表该文件是一个class类型的文件,不信,你可以多打开几个class文件看看。
(2)接下来的四个字节代表jdk的版本
如上的内容代表jdk的版本为1.8。
PS:jdk1.1的版本数字为45,以后每跨一个大版本,数字+1,所以jdk1.8的版本数字为51(十进制),转化为16进制即为34。
(3)下面一个概念是常量池
以上内容是常量池的计数器,通过该数字,我们计算出常量的个数为15个(计算出的数字减1,因为该计数器的起始数不是0,而是1)
我们用javap命令打开常量池,证明常量的确是15个。
(4)常量池后面就是访问标志,访问标志主要分为如下类别
我们回头去看看这段class的源码(居然如此简单)
Java 代码
public class ByteCode {
public ByteCode(){
}
}
该类非接口,非抽象类,非枚举,非系统代码,非final,有pulbic,且编译器在jdk1.2之后,所以,满足条件的标志为:
ACC_PUBLIC和 ACC_SUPER,对应标志数为0001和0020,合并起来就是0021。如下图位置:
(5)类索引,父类索引和接口索引
- 上文访问标志后面就是类索引,索引值为0002,对应常量池第二位。
- 类索引后面就是夫类索引,索引值为0003,对应常量池第三位。
- 父类索引后面就是接口索引,索引值为0000,代表该类没有实现任何接口。
(6)字段表,方法表,属性表
三大索引之后就是字段表
字段表为0000,代表无字段。
如上图,方法表分为四部分
- 方法表计数器的结果为1,代表有一个字段
- 方法表访问标志为0001,代表public
- 方法表名称索引为0004,对应常量池第4个
- 方法表描述索引为0005,对应常量池第5个
属性表以此类推。
四.字节码指令
单独开一个章节讲讲字节码指令,它存在于方法表中,如下分类:
(1)加载和存储指令
此部分内容,见前文《一段java代码是如何执行的》)
(2)运算或算术指令
源码:
Java 代码
public class Test {
public void add(int a,int b){
System.out.println(a+b);
System.out.println(a-b);
System.out.println(a*b);
System.out.println(a/b);
}
}
字节码指令如下:
(3)类型转换指令
源码:
Java 代码
public class Test {
public void add(int a,int b){
int c = 1;
long d = c;
}
}
字节码指令:
(4)创建实例指令
这个不用多讲,就是new
(5)创建数组指令
源码:
Java 代码
public class Test {
public void add(int a,int b){
int[] c = new int[4];
String[] d = new String[5];
}
}
字节码指令:
(6)访问字段指令
源码:
Java 代码
public class Test {
private static String name = "1";
private String age = "2";
public static void main(String[] args) {
Test test = new Test();
String a = test.age;
String b = Test.name;
}
}
字节码指令:
(7)数组存取指令
源码:
Java 代码
public static void main(String[] args) {
String[] a = new String[5];
a[1] = "2";
String b = a[1];
}
字节码指令:
(8)检查实例类型指令
就是instanceof,演示略
(9)方法返回指令
就是return,演示略
五.异常操作
直接看一段代码:
Java 代码
public class Test {
public void test() {
InputStream in = null;
try {
in = new FileInputStream("i.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
代码是一段典型的文件流操作,与其他代码不同的是,它捕获了两个异常。
那么,字节码指令又是如何处理该异常的呢
我们可以看到,最底下出现了一个exception table,即异常表,它记录了所有的异常数据
以异常表第一行举例,from,to分别代表,如果第12行,到第16行间发生异常,则直接跳到第19行(target)。
六.装箱拆箱
这是绕不过去的一个话题。
但凡有一点java基础的人都知道,java有八大基础数据类型,每一种类型都对应一种包装类。如int之于Integer,long之于Long。
一般来讲,基础数据类型和包装类都可以相互赋值。但这其中的逻辑如何呢?
Java 代码
public class Test {
public static void main(String[] args) {
Integer i = 1;
int a = 2;
int b = 3;
i = a;
b = i;
}
}
我们来看看字节码指令
从字节码指令中,我们可以看到,有三次拆装操作
- 第一次,调用Integer的valueOf方法,讲常量1转为Integer类型;
- 第二次,调用Integer的valueOf方法,讲栈顶值2转为Integer类型;
- 第三次,调用intValue方法,讲Integer转为int,然后赋值给b。
前两部为装箱,后一步为拆箱。
这就是拆装箱的底层实现逻辑了。
本文分享自华为云社区《java之深入class文件》,原文作者:技术火炬手。
破解class文件的第一步:深入理解JAVA Class文件的更多相关文章
- 文件IO 相关的包:java.io文件——API
文件IO 相关的包:java.io文件——API 1.Java.io.File类的使用(1)两种路径绝对路径:相对于当前路径:当前为 “工程名”(2)File类创建,对象为一个文件/目录,可能存在或不 ...
- PHP导出excel文件,第一步先实现PHP模板导出不带数据
今天继续研究PHP导出excel文件,把复杂的事情简单化,一步步实现功能,首先实现模板文件的导出,随后再实现写入数据后导出,最终实现功能,这是基本思路.中间可以加一步,先自己写入数据导出试试,随后再数 ...
- 版本控制之GitHub — — 第一步的理解
GitHub是时下最流行的版本控制的一门“技术”,此之前svn(subversion)也是同样的作用. 至于版本控制:Git是分布式的,而svn是中心式的(或者叫集中式的)版本控制系统,这是两者之间理 ...
- Java之路第一步——第一行Java代码
main()方法是Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法. 名字必须是main: 必须是public static void 类型的: 必须接收一 ...
- 从一堆 JAR 文件中,找出一个 Java class 文件的所在配置
Find a class somewhere inside dozens of JAR files? find path/to/libs -name '*.jar' -exec grep -Hls C ...
- java入门第一步之完成jdk的安装(window)【转】
为了面向更多的人类,我决定重温我的java起步: 要进行java开发第一步就是进行java环境的安装,也就是jdk的按装: 1.由于java被oracle收购了,我们下载jdk也就去oracle的官网 ...
- 五步整理你的css文件
鉴于实在无法忍受那种写一句就换一行的css写法,有个项目中的一个css文件竟然高达6000多行,看着实在蛋疼,无实今天下定决心整理一下,在DW里可以用正则很好的进行替换,步骤如下: 一:\r => ...
- Day3:学习Java的第一步:Hello World!
HelloWorld 新建一个文件夹存放代码 新建一个JAVA文件 文件后缀名为.java Hello.java 打开文件扩展名,即可查看到文件类型 运行文件:右键Hello.java文件用notep ...
- 深入理解Java虚拟机--中
深入理解Java虚拟机--中 第6章 类文件结构 6.2 无关性的基石 无关性的基石:有许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode),从而 ...
随机推荐
- List和String的非空判断
1.如果想判断list是否为空,可以这么判断: if(null == list || list.size() ==0 ){ //为空的情况 }else{ //不为空的情况 } 2.list.isEmp ...
- 微信小程序:应用生命周期
小程序的生命周期分为应用生命周期和页面生命周期. 应用指的是一个文件,是小程序的入口文件app.js,入口文件最外层方法名称是App,页面的js文件最外层是page,组件的js文件的最外层是compo ...
- Vue框架:vue-cookies组件
目录 一.vue-cookies简介 二.vue-cookies安装与配置 三.vue-cookies的使用 一.vue-cookies简介 vue-cookies组件是vue框架用来操作浏览器coo ...
- ElasticSearch 集群安全
公号:码农充电站pro 主页:https://codeshellme.github.io 在安装完 ES 后,ES 默认是没有任何安全防护的. ES 的安全管理主要包括以下内容: 身份认证:鉴定访问用 ...
- ElasticSearch学习笔记(超详细)
文章目录 初识ElasticSearch 什么是ElasticSearch ElasticSearch特点 ElasticSearch用途 ElasticSearch底层实现 ElasticSearc ...
- 关于Laravel框架中Guard的底层实现
1. 什么是Guard 在Laravel/Lumen框架中,用户的登录/注册的认证基本都已经封装好了,开箱即用.而登录/注册认证的核心就是: 用户的注册信息存入数据库(登记) 从数据库中读取数据和用户 ...
- 一款免费的在线 Markdown 笔记,类似 typora 编辑体验
为什么要开发一款新的编辑器 自从我开始使用 Markdown,就爱上了这种标记语法,轻量.纯文本兼容是最大的优点,哪里都可以编辑,一开始是在 IDE 上直接编辑,后来笔记越来越多,需要上传图片,有云同 ...
- WPF 基础 - Binding 的源与路径
1. 源与路径 把控件作为 binding 源与 binding 标记拓展: 控制 Binding 的方向及数据更新: Binding 的路径 Path: 没有路径的 Binding: 为 Bindi ...
- 微软跨平台UI框架MAUI真的要来啦
.NET 6 preview已经上线,是时候为在BUILD 2020上宣布的新.NET Multi-platform App UI(MAUI)做准备了.对于客户端应用程序开发人员来说,这一年.NET有 ...
- Java工程师核心书单推荐
随便打开一个招聘网站,看看对高级Java工程师的技能要求. 抛开其它的经验能力等等,单纯从技术,或者说知识上来讲,可以发现一些共通的地方. Java基础 计算机基础 数据库,SQL/NoSQL 常用开 ...