1.Class文件基础

 
(1)文件格式
 
 
Class文件的结构不像XML等描述语言那样松散自由。由于它没有任何分隔符号,
所以,以上数据项无论是顺序还是数量都是被严格限定的。哪个字节代表什么
含义,长度是多少,先后顺序如何,都不允许改变。
 
(2)数据类型
 
仔细观察上面的Class文件格式,可以看出Class文件格式采用一种类似于C语言
结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表
无符号数就是u1、u2、u4、u8来分别代表1个、2个、4个、8个字节。表是由
多个无符号数或其他表构成的复合数据类型,以“_info”结尾。在表开始位置,
通常会使用一个前置的容量计数器,因为表通常要描述数量不定的多个数据。
 
下图表示的就是Class文件格式中按顺序各个数据项的类型:
 
 
(3)兼容性
 
高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,
即使文件格式未发生任何变化。举例来说,JDK 1.7中的JRE能够执行JDK 1.5编译
出的Class文件,但是JDK 1.7编译出来的Class文件不能被JDK 1.5使用。这就是
target参数的用处,可以在使用JDK 1.7编译时指定-target 1.5。
 
 
2.一个简单的例子
  1. package com.cdai.jvm.bytecode;
  2. public class ByteCodeSample {
  3. private String msg = "hello world";
  4. public void say() {
  5. System.out.println(msg);
  6. }
  7. }
编译成Class文件后的样子:
 
 
 
3.逐个字节分析
 
(1)魔数和版本号
 
 
前四个字节(u4)cafebabe就是Class文件的魔数,第5、6字节(u2)是Class文件的
次版本号,第7、8字节(u2)是主版本号。十六进制0和32,也就是版本号为50.0,
即JDK 1.6。之前介绍的target参数会影响这四个字节的值,从而使Class文件兼容不同
的JDK版本。
 
(2)常量池
 
 
常量池是一个表结构,并且就像之前介绍过的,在表的内容前有一个u2类型的计数器,
表示常量池的长度。十六进制23的十进制值为35,表示常量池里有下标为1~34的表项。
下标从1开始而不是0,是因为第0个表项表示“不引用常量池中的任意一项”。每个表项
的第一个字节是一个u1类型,表示12中数据类型。具体含义如下:
 
 
以第一项07 00 02为例,07表示该常量是个CONSTANT_Class_info类型,紧接着一个u2
类型的索引执行第2项常量。再看第二项01 00 24 63 6f 6d 2f ... 65表示的就是字符串
类型,长度为36(十六进制00 24),紧接着就是UTF-8编码的字符串"com/cdai/jvm/bytecode
/ByteCodeSample"。很容易读懂吧?常量池主要是为后面的字段表和方法表服务的。
 
下面是通过javap解析后常量池的全貌(执行javap -c -l -s -verbose ByteCodeSample
 
  Constant pool:
const #1 = class        #2;     //  com/cdai/jvm/bytecode/ByteCodeSample
const #2 = Asciz        com/cdai/jvm/bytecode/ByteCodeSample;
const #3 = class        #4;     //  java/lang/Object
const #4 = Asciz        java/lang/Object;
const #5 = Asciz        msg;
const #6 = Asciz        Ljava/lang/String;;
const #7 = Asciz        <init>;
const #8 = Asciz        ()V;
const #9 = Asciz        Code;
const #10 = Method      #3.#11; //  java/lang/Object."<init>":()V
const #11 = NameAndType #7:#8;//  "<init>":()V
const #12 = String      #13;    //  hello world
const #13 = Asciz       hello world;
const #14 = Field       #1.#15; //  com/cdai/jvm/bytecode/ByteCodeSample.msg:Ljava/lang/String;
const #15 = NameAndType #5:#6;//  msg:Ljava/lang/String;
const #16 = Asciz       LineNumberTable;
const #17 = Asciz       LocalVariableTable;
const #18 = Asciz       this;
const #19 = Asciz       Lcom/cdai/jvm/bytecode/ByteCodeSample;;
const #20 = Asciz       say;
const #21 = Field       #22.#24;        //  java/lang/System.out:Ljava/io/PrintStream;
const #22 = class       #23;    //  java/lang/System
const #23 = Asciz       java/lang/System;
const #24 = NameAndType #25:#26;//  out:Ljava/io/PrintStream;
const #25 = Asciz       out;
const #26 = Asciz       Ljava/io/PrintStream;;
const #27 = Method      #28.#30;        //  java/io/PrintStream.println:(Ljava/lang/String;)V
const #28 = class       #29;    //  java/io/PrintStream
const #29 = Asciz       java/io/PrintStream;
const #30 = NameAndType #31:#32;//  println:(Ljava/lang/String;)V
const #31 = Asciz       println;
const #32 = Asciz       (Ljava/lang/String;)V;
const #33 = Asciz       SourceFile;
const #34 = Asciz       ByteCodeSample.java;          
 
(3)访问标志
 
 
 
显然,00 21表示的就是公有的类。
 
(4)类、父类、接口
 
 
这三个u2类型的值分别表示类索引1、父类索引3、接口索引集合0。查看之前的常量池,
第1项为"com/cdai/jvm/bytecode/ByteCodeSample",第3项为"java/lang/Object"。第0项
表示此类没有实现任何接口,这也就是常量池第0项的作用!
 
(5)字段表
 
 
00 01表示有1个字段。00 02是字段的访问标志,表示private权限的。00 05是字段的名称
索引,指向常量池里第5项"msg"。00 06是字段的描述符索引,指向常量池里的第6项
"Ljava/lang/String"。最后的00 00表示该字段没有其他属性表了。
 
描述符的作用就是用来描述字段的数据类型、方法的参数列表和返回值。而属性表就是为
字段表和方法表提供额外信息的表结构。对于字段来说,此处如果将字段声明为一个static
final msg = "aaa"的常量,则字段后就会跟着一个属性表,其中存在一项名为ConstantValue,
指向常量池中的一个常量,值为的"aaa"。
 
属性表不像Class文件中的其他数据项那样具有严格的顺序、长度和内容,任何人实现的编译器
都可以向属性表中写入自己定义的属性信息,JVM会忽略掉它不认识的属性。后面的方法表中
还要用到属性表的Code属性,来保存方法的字节码。
 
(6)方法表
 
 
00 02表示有两个方法。00 01是方法的访问标志,表示公有方法。00 07和00 08与字段表中的名称
和描述符索引相同,在这里分别表示"<init>"和"()V"。00 01表示该方法有属性表,属性名称为00 09
即我们前面提到的Code属性。
 
要注意的是:Code属性表也可以有自己的属性,如后面的LocalVariableTable和LineNumberTable。
它们分别为JVM提供方法的栈信息和调试信息。
 
以下是javap解析后的结果:
 
public com.cdai.jvm.bytecode.ByteCodeSample();
  Signature: ()V
  LineNumberTable:
   line 3: 0
   line 5: 4
   line 3: 10

LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      11      0    this       Lcom/cdai/jvm/bytecode/ByteCodeSample;

Code:
   Stack=2, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #10; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   ldc     #12; //String hello world
   7:   putfield        #14; //Field msg:Ljava/lang/String;
   10:  return
  LineNumberTable:
   line 3: 0
   line 5: 4
   line 3: 10

LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      11      0    this       Lcom/cdai/jvm/bytecode/ByteCodeSample;

public void say();
  Signature: ()V
  LineNumberTable:
   line 8: 0
   line 9: 10

LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      11      0    this       Lcom/cdai/jvm/bytecode/ByteCodeSample;

Code:
   Stack=2, Locals=1, Args_size=1
   0:   getstatic       #21; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   aload_0
   4:   getfield        #14; //Field msg:Ljava/lang/String;
   7:   invokevirtual   #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   10:  return
  LineNumberTable:
   line 8: 0
   line 9: 10

LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      11      0    this       Lcom/cdai/jvm/bytecode/ByteCodeSample;          

 

4.小结
 
怎么样并不难吧!接下来我们将要学习下如何用字节码工具如ASM、CGLIB来手写
字节码,从而加深对字节码的理解。

学会阅读Java字节码的更多相关文章

  1. 在Eclipse里查看Java字节码

    要理解 Java 字节码,比较推荐的方法是自己尝试编写源码对照字节码学习.其中阅读 Java 字节码的工具必不可少.虽然javap可以以可读的形式展示出.class 文件中字节码,但每次改动源码都需调 ...

  2. 【转】在Eclipse里查看Java字节码

    要理解 Java 字节码,比较推荐的方法是自己尝试编写源码对照字节码学习.其中阅读 Java 字节码的工具必不可少.虽然javap可以以可读的形式展示出.class 文件中字节码,但每次改动源码都需调 ...

  3. 通过Java字节码发现有趣的内幕之String篇(上)(转)

    原文出处: jaffa 很多时候我们在编写Java代码时,判断和猜测代码问题时主要是通过运行结果来得到答案,本博文主要是想通过Java字节码的方式来进一步求证我们已知的东西.这里没有对Java字节码知 ...

  4. Java:从面试题“i++和++i哪个效率高?"开始学习java字节码

    今天看到一道面试题,i++和++i的效率谁高谁低. 面试题的答案是++i要高一点. 我在网上搜了一圈儿,发现很多回答也都是同一个结论. 如果早个几年,我也会认同这个看法,但现在我负责任的说,这个结论是 ...

  5. 空手套白狼,硬阅java字节码class文件

    如下,是一些java字节码也就是原始的class文件,当应用部署到线上之后,我们能够看到的也就是这样的字样了.那么怎样解呢?就让我们一起,来解读解读字节码吧! Offset A B C D E F C ...

  6. JVM 内部原理(七)— Java 字节码基础之二

    JVM 内部原理(七)- Java 字节码基础之二 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...

  7. JVM 内部原理(六)— Java 字节码基础之一

    JVM 内部原理(六)- Java 字节码基础之一 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...

  8. java字节码速查笔记

    java字节码速查笔记  发表于 2018-01-27 |  阅读次数: 0 |  字数统计: |  阅读时长 ≍ 执行原理 java文件到通过编译器编译成java字节码文件(也就是.class文件) ...

  9. 从Java源码到Java字节码

    Java最主流的源码编译器,javac,基本上不对代码做优化,只会做少量由Java语言规范要求或推荐的优化:也不做任何混淆,包括名字混淆或控制流混淆这些都不做.这使得javac生成的代码能很好的维持与 ...

随机推荐

  1. SharePoint 使用ECMAscript对象模型来操作Goup与User

    这里总结了关于使用ECMAscript对象模型来操作Goup与User的常用情况,内容如下:     1.取得当前Sharepoint网站所有的Groups     2.获取当前登录用户的Title与 ...

  2. 磁盘IO概念及优化入门知识

    在数据库优化和存储规划过程中,总会提到IO的一些重要概念,在这里就详细记录一下,对这个概念的熟悉程度也决定了对数据库与存储优化的理解程度,以下这些概念并非权威文档,权威程度肯定就不能说了. 读/写IO ...

  3. 4. Beego 框架之cookie与session

    what is cookie? cookie是存储在客户端的,用于标识客户身份的! what is session session 是存储在服务端,也是用于客户身份标识,用于跟踪用户会话. BeeGo ...

  4. 12 go实现几中基本排序算法

    include 冒泡排序 插入排序 快速排序 选择排序 这4种算法的内涵不再做解释了 github地址 冒泡排序算法 func maoPao(intSlice []int) []int { /* 冒泡 ...

  5. Javascript获取IFrame内容(兼容IE&FF)

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://wintys.blog.51cto.com/425414/123303 作者: W ...

  6. Spring getBean 首字母大小写问题

    如果类第一个字母大写第二个小写,那么首字母小写获取bean 如果第一个和第二个字母都是大写的,那个获取bean首字母要大写

  7. 转载nginx+uwsgi+django

    Django的部署可以有很多方式,采用nginx+uwsgi的方式是其中比较常见的一种方式. 在这种方式中,我们的通常做法是,将nginx作为服务器最前端,它将接收WEB的所有请求,统一管理请求.ng ...

  8. Linux应急响应(四):盖茨木马

    0x00 前言 ​ Linux盖茨木马是一类有着丰富历史,隐藏手法巧妙,网络攻击行为显著的DDoS木马,主要恶意特点是具备了后门程序,DDoS攻击的能力,并且会替换常用的系统文件进行伪装.木马得名于其 ...

  9. Memcache未授权访问漏洞

    Memcached 分布式缓存系统,默认的 11211 端口不需要密码即可访问,黑客直接访问即可获取数据库中所有信息,造成严重的信息泄露. 0X00 Memcache安装 1. 下载Mencache的 ...

  10. Git和GitHub入门基础

    -----------------------------------------//cd F:/learngit // 创建仓库git init  // 在当前目录下创建空的git仓库------- ...