JVM中class文件探索与解析(一)

一直想成为一名优秀的架构师的我,转眼已经工作快两年了,对于java内核了解甚少,闲来时间,看看JVM,吧自己的一些研究写下来供大家参考,有不对的地方请指正。

废话不多说,一起来看看JVM中类文件是如何加载和运行的。

(1)首先,编写简单代码,对其编译生成的class文件进行研究,其java代码如下:

 1 public class test {
 2     private static int count = 0;
 3     public static void recursion(){
 4         count++;
 5         recursion();
 6     }
 7
 8     public  static  void  main (String args[]){
 9         try {
10             recursion();
11         }catch (Exception ex){
12             System.out.print("deep of callings:"+count+"\n");
13             ex.printStackTrace();
14         }
15     }
16 }

编译之后,用WinHex软件打开其class文件,可以看到其编译的十六进制文件如下:

  按照上图分析,开头的前4个字节,是魔数(类似于拼音“咖啡宝贝”),它的用处是标识该文件是否能被java虚拟机识别;

  紧接着魔数的4个字节,前两字节0x00代表次版本号(小数点之后的数字),后两字节0x0033代表是class文件的主版本号,换算成十进制是51,标识是JDK1.7可识别的版本(不同的版本可以查看class文件版本号表如下:)

版本号 对应十进制 jdk版本号
2E 46 jdk1.2
2F 47 jdk1.3
30 48 jdk1.4
31 49 jdk1.5
32 50 jdk1.6
33 51 jdk1.7
34 52 jdk1.8

  在主版本号字节之后的是常量池,可以理解为Class文件的资源仓库,存储着与class文件相关的数据项。由于不同class文件,常量池数量不同,常量池入口放置两个字节的数据(0x0028)为常量池计数器。十六进制的0x0028为十进制的40(地址偏移量),代表常量池中有39个常量,索引范围为1-40(注:java仅限于class文件结构中容量计数器是从1开始的,java的设计者将索引0拿出来是有特殊考虑的,用来表示不引用任何一个常量池中的项)。

  常量池中,存放两类数据:(1)字面量:可以理解为java中的常量,例如:字符串、final修饰常量等。

              (2)符号引用:主要包括①类、接口的全限定名②字段的名称和描述符③方法的名称和描述符

  在常量池里,存储常量结构如下:u1(常量标志位,用于指明常量的类型,可以查看如下常量池项目类型对应表)+常量信息

  让我们以上述class文件为例,索引为1的常量标志位是0x0A(十进制为10),对应上表中的CONSTANT_Methoddef_info类型的常量,参考常量结构表如下图(在jdk1.7中新增了tag=15/16/18的常量类型,更好的支持动态语言的调用,此处就不列举了),

  该class文件中,常量池里索引为1的常量(const#1),项目类型标识符为0x0A,二进制为10,查询上表,代表着类方法的符号引用。紧接着两个u2字符代表该常量的信息内容,其中方法描述符0x0006为#6常量,名称及类型描述为0x001A指向#26常量。

  紧跟其后的是索引为2的常量(const#2),其标志符为0x09(十进制为9),是字段的符号引用,紧接着的两个u2字符代表其引用索引ID,方法的类描述符指向#27常量,字段描述符指向#28常量;

  分析了以上两个字节之后,这里就不一一分析后面的常量了,有兴趣的可以自己分析下。其他的常量池用jdk自带的javap进行生成,在windows中打开cmd(安装jdk并配置环境变量),输入:javap -verbose class文件路径,可以看到编译之后的常量池如下:

  将上述class文件中常量池部分标记图如下,红色框代表一个常量池中的项,依次编号为1-39,

  我们将上图和javap生成的常量内容对比一下,以const#9为例,#9项为:0x01 (utf8类型) 0x0006(占用字节) 0x3C 0x69 0x6E 0x69 0x74 0x3E(项内容),我们对项内容进行在线转换,将十六进制转换ASCII码值,得到该常量表示:<init>,如下图:

  与javap生成的常量文件对比,发现两者完全一致。对字节码有兴趣的朋友可以逐个试一试。

  在常量池区域结束之后,紧接着的一个u2(两个字节)类型的字符代表访问标志,它用于识别类或者接口的访问信息,例如:class是类还是接口,访问是private还是public等。访问标志表如下图:

  在上述文件中,访问标志为:,即:0x0021,对照上表,只有ACC_PUBLIC和ACC_SUPER为真,其他几项为假。该类为public 能够使用invoke指令。

  跟在访问标志之后的分别是类索引、父类索引。由于java不允许多继承,所以类索引和父类索引是一个u2类型的数据。在上述文件中,类索引为#5常量(TestClass),父类索引为#6常量(java/lang/Object);

  紧接着类索引和父类索引的是接口索引信息。在java中一个类可以实现多个接口,所以用u2类型的数据集合来表示接口索引。在接口索引的入口,有一项u2类型的接口计数器,计数器为0表示接口的索引表不占用任何字节。

  在接口相关描述信息之后的,是字段表集合,用于描述类或者接口中申明的变量(注:此处的变量是指类或者接口级变量,即类变量或者实例级变量,而不包括方法中的局部变量)。这些字段通常包含哪些信息呢?通常有:字段访问域(private、public、protected等)+是实例变量还是类变量(Static)+是否可修改(final)+并发可见性(volatitle,用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最终的值)+可否序列化(transient)+字段类型(基本类型、对象、数组)+字段名称。在JVM中,字段表结构如下:

类型     名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

  我们来看,字段表集合的入口u2类型数据项是字段表的容量计数器为(0x0001),表示只有一个字段项。紧接着字段表容量计数器的u2类型的数据为0x0002,参考下表,表示该字段为private。字段名称为0x0007,其值为“m”,描述信息0x0008,其值为“I”,可以推断,原代码的定义字段为:“private int m”;

标志名称 标志值 含义
ACC_PUBLIC 0x00 01 字段是否为public
ACC_PRIVATE 0x00 02 字段是否为private
ACC_PROTECTED 0x00 04 字段是否为protected
ACC_STATIC 0x00 08 字段是否为static
ACC_FINAL 0x00 10 字段是否为final
ACC_VOLATILE 0x00 40 字段是否为volatile
ACC_TRANSTENT 0x00 80 字段是否为transient
ACC_SYNCHETIC 0x10 00 字段是否为由编译器自动产生
ACC_ENUM 0x40 00 字段是否为enum

  通常而言,在字段描述之后还有一些属性表信息存储额外的信息,在以上class文件中,属性计数器位0x0000,表示没有额外的属性信息。

  在字段表之后的是方法表集合,其表示方法与字段信息表几乎一致。其结构如下表:

类型     名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

  但是,方法表的修饰属性比字段表要多,例如:方法有abstract、synchronize等。在方法表的入口,同样也有一个方法容量计数器,占用u2字节。在上述class中,方法的计数器为,即0x0003表示有三个方法。

  第一个方法,function#1的第一、二、三、四、五项u2数据项分别为:0x0001、0x0009、0x000A、0x0001、0x000B,代表方法为public、方法名指向const#9("<init>")、方法描述为const#10(“()V”)、含有一个属性、该属性指向const#11(“Code”)属性,java呈现方法体重的代码经过javac编译之后,就存储在Code属性里。

  (ps:今天的class文件探索就写到这里,过几天接着写...休息一下)

  

  

  

时间: 2024-10-08 11:13:28

JVM中class文件探索与解析(一)的相关文章

MFC Wizard创建的空应用程序中各个文件内容的解析

创建的MFC应用程序名为:wd,那么: 一.wd.h解析 // wd.h : main header file for the WD application // #if !defined(AFX_WD_H__89BE48D2_F377_4DF1_8C44_4D7372A61CE0__INCLUDED_) #define AFX_WD_H__89BE48D2_F377_4DF1_8C44_4D7372A61CE0__INCLUDED_ //////////////////////////////

C++ 中使用boost::property_tree读取解析ini文件

boost 官网 http://www.boost.org/ 下载页面 http://sourceforge.net/projects/boost/files/boost/1.53.0/ 我下载的是 boost_1_53_0.tar.gz 使用系统  ubuntu 12.10 一.解压 [plain] view plaincopy tar -zxvf  boost_1_53_0.tar.gz 得到一个文件夹 boost_1_53_0,  拷贝其子目录 boost 到以下路径 [plain] vi

js上传文件带参数,并且,返回给前台文件路径,解析上传的xml文件,存储到数据库中

ajaxfileupload.js jQuery.extend({ createUploadIframe: function(id, uri) { //create frame var frameId = 'jUploadFrame' + id; if(window.ActiveXObject) { var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '&qu

FFmpeg中HLS文件解析源码

不少人都在找FFmpeg中是否有hls(m3u8)解析的源码,其实是有的.就是ffmpeg/libavformat/hlsproto.c,它依赖的文件也在那个目录中. /* * Apple HTTP Live Streaming Protocol Handler * Copyright (c) 2010 Martin Storsjo * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute

探索Java语言与JVM中的Lambda表达式

Lambda表达式是自Java SE 5引入泛型以来最重大的Java语言新特性,本文是2012年度最后一期Java Magazine中的一篇文章,它介绍了Lamdba的设计初衷,应用场景与基本语法.(2013.01.02最后更新) Lambda表达式,这个名字由该项目的专家组选定,描述了一种新的函数式编程结构,这个即将出现在Java SE 8中的新特性正被大家急切地等待着.有时你也会听到人们使用诸如闭包,函数直接量,匿名函数,及SAM(Single Abstract Method)这样的术语.其

关于JVM中方法调用的相关指令,以及解析(Resolution)和分派(Dispatch)的解释——重载的实现原理与重写的实现原理

JVM中相关方法的调用的指令 invokestatic 调用静态方法. invokespecial 用于调用构造器方法<init>.私有方法.父类方法. invokevirtual 用于调用类的所有虚方法. invokeinterface 用于调用接口方法. 解析(resolution)与分派(dispatch) 解析 解析调用一定是个静态的过程,在编译期间就完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变 为可确定的直接引用,不会延迟到运行期再去完成. 下面我们看一段代码: /**

【Python】解析Python中的文件操作

目录结构: contents structure [-] 简介 Python中的文件类型 内置函数的文件操作 open()函数 Mode 创建文本文件 读取文本文件 循环文件对象 关闭文件 With语句 os模块 fileinput模块 1.简介 在Python中无需引入额外的模块来进行文件操作,Python拥有内置的文件操作函数(除了内置文件操作函数,Python语言也提供了额外的文件操作模块,它们具有更加强大的功能). os模块提供了在操作系统上可移植的文件操作方法.如果只是想要读取和写入数

深入Java虚拟机:JVM中的Stack和Heap

转自:http://www.cnblogs.com/laoyangHJ/archive/2011/08/17/gc-Stack.html —————————————————————————————————————————————— 在JVM中,内存分为两个部分,Stack(栈)和Heap(堆),这里,我们从JVM的内存管理原理的角度来认识Stack和Heap,并通过这些原理认清Java中静态方法和静态属性的问题. 一般,JVM的内存分为两部分:Stack和Heap. Stack(栈)是JVM的内

从字节码指令看重写在JVM中的实现

Java是解释执行的,包括动态链接的特性,都给解析或运行期间提供了很多灵活扩展的空间.面向对象语言的继承.封装和多态的特性,在JVM中是怎样进行编译.解析,以及通过字节码指令如何确定方法调用的版本是本文如下要探讨的主要内容,全文围绕一个多态的简单举例来看在JVM中是如何实现的. 先简单介绍几个概念.对于字节码执行模型及字节码指令集的相关概念可以参考之前的一篇介绍http://blog.csdn.net/lijingyao8206/article/details/46562933. 一.方法调用的