• 让创新无法想象。咨询热线:022-60709568
  • 关注我们

java虚拟机初识

阅读:406 2019-03-19 14:41:18 来源:新网

学习java虚拟机规范主要是为了了解java代码的运行机制,然后能够更深刻理解热修复的原理以及方法,当然不仅仅如此,感觉想要在android上更深入研读,虚拟机原理是个绕不过的坎。

关于java虚拟机的规范我们可以在官方文档中找到:https://docs.oracle.com/javase/specs/jvms/se8/html/

网上也有中文翻译的pdf《java虚拟机规范》百度搜一下就行。ps:笔者还是买了一本实体版的书……

当然本文并不是来显摆笔者有实体书的,主要用来记录下学习的过程。

本文将出现非常多的转载和摘录,原出处会在末尾列出。

class文件到底是什么东西?我觉得一种通俗易懂的解释就是:

在某种哲学意义上看,java源文件和处理得到的class文件是同一种东西......

字节码结构比较特殊,其内部不包含任何的分隔符,无法人工区分段落(字节码文件本身就是给机器读的),所以无论是字节顺序、数量都是有严格规定的,所有16位、32位、64位长度的数据都将构造成2个、4个、8个-----8位字节单位来表示,多字节数据项总是按照big-endian顺序(高位字节在地址的最低位,低位字节在地址的最高位)来进行存储。

参考《java虚拟机规范javase7版》的描述,每一个字节码其实都对应着全局唯一的一个类或者接口的定义信息。字节码文件才用的是一种类似于c语言结构体的伪结构来描述字节码文件格式。字节码文件中对应的“基本类型”u1,u2,u4,u8分别表示无符号1、2、4、8个字节。

那么,这个给vm使用的class文件,其内部结构是怎样的呢?jvm规范很聪明,它通过一个c的数据结构表达了class文件结构。这个数据结构如图2所示:

class文件用javap工具可以很好得解析成图2那样的格式,我这里替大家解析了一把

假设存在类如下

publicclassclasstest{publicstaticintmfield;publicstringmname="yxwang";publicstringgetname(){returnmname;}publicvoidsetname(stringname){mname=name;}publicstaticvoidtest1(){mfield=1;}publicclasstest(){}publicclasstest(stringname){mname=name;}}

使用andoidsdkbuildtools中工具dx:dx--dump--debugxxx.class

得到结果如下:

beginclassfilemagic:cafebabeminor_version:0000major_version:0033constant_pool_count:0020constant_pool:0001:field{com.guardz.classtest.mname:ljava/lang/string;}0002:field{com.guardz.classtest.mfield:i}0003:method{java.lang.object.:()v}0004:string{"yxwang"}0005:type{com.guardz.classtest}0006:type{java.lang.object}0007:utf8{"mfield"}0008:utf8{"i"}0009:utf8{"mname"}000a:utf8{"ljava/lang/string;"}000b:utf8{"getname"}000c:utf8{"()ljava/lang/string;"}000d:utf8{"code"}000e:utf8{"linenumbertable"}000f:utf8{"localvariabletable"}0010:utf8{"this"}0011:utf8{"lcom/guardz/classtest;"}0012:utf8{"setname"}0013:utf8{"(ljava/lang/string;)v"}0014:utf8{"name"}0015:utf8{"test1"}0016:utf8{"()v"}0017:utf8{""}0018:utf8{"sourcefile"}0019:utf8{"classtest.java"}001a:nat{mname:ljava/lang/string;}001b:nat{mfield:i}001c:nat{:()v}001d:utf8{"yxwang"}001e:utf8{"com/guardz/classtest"}001f:utf8{"java/lang/object"}endconstant_poolaccess_flags:public|superthis_class:type{com.guardz.classtest}super_class:type{java.lang.object}interfaces_count:0000fields_count:0002fields[0]:access_flags:public|staticname:mfielddescriptor:iattributes_count:0000endfields[0]fields[1]:access_flags:publicname:mnamedescriptor:ljava/lang/string;attributes_count:0000endfields[1]methods_count:0005methods[0]:access_flags:publicname:getnamedescriptor:()ljava/lang/string;attributes_count:0001attributes[0]:name:codelength:0000002fmax_stack:0001max_locals:0001code_length:000000050000:aload_0//000001:getfieldfield{com.guardz.classtest.mname:ljava/lang/string;}0004:areturnexception_table_length:0000attributes_count:0002attributes[0]:name:linenumbertablelength:00000006line_number_table_length:0001000013endattributes[0]attributes[1]:name:localvariabletablelength:0000000clocal_variable_table_length:00010000..00050000thislcom/guardz/classtest;endattributes[1]endattributes[0]endmethods[0]methods[1]:access_flags:publicname:setnamedescriptor:(ljava/lang/string;)vattributes_count:0001attributes[0]:name:codelength:0000003emax_stack:0002max_locals:0002code_length:000000060000:aload_0//000001:aload_1//010002:putfieldfield{com.guardz.classtest.mname:ljava/lang/string;}0005:returnexception_table_length:0000attributes_count:0002attributes[0]:name:linenumbertablelength:0000000aline_number_table_length:0002000017000518endattributes[0]attributes[1]:name:localvariabletablelength:00000016local_variable_table_length:00020000..00060000thislcom/guardz/classtest;0000..00060001nameljava/lang/string;endattributes[1]endattributes[0]endmethods[1]methods[2]:access_flags:public|staticname:test1descriptor:()vattributes_count:0001attributes[0]:name:codelength:00000021max_stack:0001max_locals:0000code_length:000000050000:iconst_1//#+010001:putstaticfield{com.guardz.classtest.mfield:i}0004:returnexception_table_length:0000attributes_count:0001attributes[0]:name:linenumbertablelength:0000000aline_number_table_length:0002000021000422endattributes[0]endattributes[0]endmethods[2]methods[3]:access_flags:publicname:descriptor:()vattributes_count:0001attributes[0]:name:codelength:0000003dmax_stack:0002max_locals:0001code_length:0000000b0000:aload_0//000001:invokespecialmethod{java.lang.object.:()v}0004:aload_0//000005:ldcstring{"yxwang"}0007:putfieldfield{com.guardz.classtest.mname:ljava/lang/string;}000a:returnexception_table_length:0000attributes_count:0002attributes[0]:name:linenumbertablelength:0000000eline_number_table_length:0003000024000411000a26endattributes[0]attributes[1]:name:localvariabletablelength:0000000clocal_variable_table_length:00010000..000b0000thislcom/guardz/classtest;endattributes[1]endattributes[0]endmethods[3]methods[4]:access_flags:publicname:descriptor:(ljava/lang/string;)vattributes_count:0001attributes[0]:name:codelength:00000050max_stack:0002max_locals:0002code_length:000000100000:aload_0//000001:invokespecialmethod{java.lang.object.:()v}0004:aload_0//000005:ldcstring{"yxwang"}0007:putfieldfield{com.guardz.classtest.mname:ljava/lang/string;}000a:aload_0//00000b:aload_1//01000c:putfieldfield{com.guardz.classtest.mname:ljava/lang/string;}000f:returnexception_table_length:0000attributes_count:0002attributes[0]:name:linenumbertablelength:00000012line_number_table_length:0004000028000411000a29000f30endattributes[0]attributes[1]:name:localvariabletablelength:00000016local_variable_table_length:00020000..00100000thislcom/guardz/classtest;0000..00100001nameljava/lang/string;endattributes[1]endattributes[0]endmethods[4]attributes_count:0001attributes[0]:name:sourcefilelength:00000002source:string{"classtest.java"}endattributes[0]endclassfile

使用:javap-verbosexxxx.class得到接内容有些区别

lastmodified2017-3-24;size738bytesmd5checksum64616b1c45519938570961653d3a655acompiledfrom"classtest.java"publicclasscom.guardz.classtestminorversion:0majorversion:51flags:acc_public,acc_superconstantpool:#1=fieldref#5.#26//com/guardz/classtest.mname:ljava/lang/string;#2=fieldref#5.#27//com/guardz/classtest.mfield:i#3=methodref#6.#28//java/lang/object."":()v#4=string#29//yxwang#5=class#30//com/guardz/classtest#6=class#31//java/lang/object#7=utf8mfield#8=utf8i#9=utf8mname#10=utf8ljava/lang/string;#11=utf8getname#12=utf8()ljava/lang/string;#13=utf8code#14=utf8linenumbertable#15=utf8localvariabletable#16=utf8this#17=utf8lcom/guardz/classtest;#18=utf8setname#19=utf8(ljava/lang/string;)v#20=utf8name#21=utf8test1#22=utf8()v#23=utf8#24=utf8sourcefile#25=utf8classtest.java#26=nameandtype#9:#10//mname:ljava/lang/string;#27=nameandtype#7:#8//mfield:i#28=nameandtype#23:#22//"":()v#29=utf8yxwang#30=utf8com/guardz/classtest#31=utf8java/lang/object{publicstaticintmfield;descriptor:iflags:acc_public,acc_staticpublicjava.lang.stringmname;descriptor:ljava/lang/string;flags:acc_publicpublicjava.lang.stringgetname();descriptor:()ljava/lang/string;flags:acc_publiccode:stack=1,locals=1,args_size=10:aload_01:getfield#1//fieldmname:ljava/lang/string;4:areturnlinenumbertable:line13:0localvariabletable:startlengthslotnamesignature050thislcom/guardz/classtest;publicvoidsetname(java.lang.string);descriptor:(ljava/lang/string;)vflags:acc_publiccode:stack=2,locals=2,args_size=20:aload_01:aload_12:putfield#1//fieldmname:ljava/lang/string;5:returnlinenumbertable:line17:0line18:5localvariabletable:startlengthslotnamesignature060thislcom/guardz/classtest;061nameljava/lang/string;publicstaticvoidtest1();descriptor:()vflags:acc_public,acc_staticcode:stack=1,locals=0,args_size=00:iconst_11:putstatic#2//fieldmfield:i4:returnlinenumbertable:line21:0line22:4publiccom.guardz.classtest();descriptor:()vflags:acc_publiccode:stack=2,locals=1,args_size=10:aload_01:invokespecial#3//methodjava/lang/object."":()v4:aload_05:ldc#4//stringyxwang7:putfield#1//fieldmname:ljava/lang/string;10:returnlinenumbertable:line24:0line11:4line26:10localvariabletable:startlengthslotnamesignature0110thislcom/guardz/classtest;publiccom.guardz.classtest(java.lang.string);descriptor:(ljava/lang/string;)vflags:acc_publiccode:stack=2,locals=2,args_size=20:aload_01:invokespecial#3//methodjava/lang/object."":()v4:aload_05:ldc#4//stringyxwang7:putfield#1//fieldmname:ljava/lang/string;10:aload_011:aload_112:putfield#1//fieldmname:ljava/lang/string;15:returnlinenumbertable:line28:0line11:4line29:10line30:15localvariabletable:startlengthslotnamesignature0160thislcom/guardz/classtest;0161nameljava/lang/string;}sourcefile:"classtest.java"

实际上只是显示上的区别,而且使用dx显示的格式更容易读懂一些,所以还是使用dx产生的内容来了解吧。

常量池看起来陌生,其实简单得要死。注意,count_pool_count是常量池数组长度+1。比如,假设某个class文件常量池只有4个元素,那么count_pool_count=5)。

javap解析class文件的时候,常量池的索引从1算起,0默认是给vm自己用得,一般不显示0这一项。这也是为什么图3中常量池第一个元素以#1开头。所以,如果count_pool_count=5的话,真正有用的元素是从count_pool[1]到count_pool[4]。

常量池数组的元素类型由下面的代码表示:

cp_info{//特别注意,这是介绍的cp_info是相关元素类型的通用表达。u1tag;//tag为1个字节长。不论cp_info具体是哪种,第一个字节一定代表tagu1info[];//其他信息,长度随tag不同而不同}//tag取值,先列几个简单的:tag=7<==info代表这个cp_info是constant_class_info结构体tag=9<==info代表constant_fieldrefs_info结构体tag=10<==info代表constant_methodrefs_info结构体tag=8<==info代表constant_string_info结构体tag=1<==info代表constant_utf8_info结构体constant_utf8_info

在jvm规范中,真正代表字符串的数据结构是constant_utf8_info结构体用来表示字符串常量值,它的结构如下代码所示:

constant_utf8_info{u1tag;u2length;//下面就是存储utf8字符串的地方了u1bytes[length];}

比如0007:utf8{"mfield"}这行表示类型是constant_utf8_info,字符串为“com/test/testmain”

另外本以为用来存储字符串的constant_string_info用于表示java.lang.string类型的常量对象,格式如下:

constant_string_info{u1tag;u2string_index;}

constant_string_info结构各项的说明如下:

tagconstant_string_info结构的tag项的值为constant_string(8)。

string_indexstring_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是constant_utf8_info(§4.4.7)结构,表示一组unicode码点序列,这组unicode码点序列最终会被初始化为一个string对象

我们在dx解析的结果上直接看到了结果:

0004:string{"yxwang"}

并不是索引,实际上是dx已经帮我们找到了索引内容并且直接显示出来,在javap解析的内容中我们可以看到,实际上是这样的:

#4=string#29//yxwang

#29=utf8yxwang

constant_class_info结构用于表示类或接口,格式如下:

constant_class_info{u1tag;u2name_index;}

name_index表示代表自己类名的字符串信息位于于常量池数组中哪一个,也就是索引

0005:type{com.guardz.classtest}或者

#5=class#30//com/guardz/classtest

#30=utf8com/guardz/classtest

结构constant_nameandtype_info结构用于表示字段或方法,但是constant_nameandtype_info结构没有标识出它所属的类或接口,格式如下:

constant_nameandtype_info{u1tag;u2name_index;//方法名或域名对应的字符串索引u2descriptor_index;//方法信息(参数+返回值),或者成员变量的信息(类型)对应的字符串索引}

dx的返回就是如下0003:method{java.lang.object.:()v}(索引值已经自动找到utf8翻译了)

他们都有类似的结构:

constant_xxxx_info{u1tag;u2class_index;u2name_and_type_index;}

class_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是constant_class_info结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。

name_and_type_indexname_and_type_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是constant_nameandtype_info结构,它表示当前字段或方法的名字和描述符。

比如dx

0001:field{com.guardz.classtest.mname:ljava/lang/string;}0002:field{com.guardz.classtest.mfield:i}0003:method{java.lang.object.:()v}

或者javap

#1=fieldref#5.#26//com/guardz/classtest.mname:ljava/lang/string;#2=fieldref#5.#27//com/guardz/classtest.mfield:i#3=methodref#6.#28//java/lang/object."":()v

每个字段(field)或者方法(method)都由field_info结构所定义,在同一个class文件中,不会有两个字段同时具有相同的字段名和描述符,描述符如下:

field_info{u2access_flags;//定义字段(方法)被访问权限和基础属性的掩码标志u2name_index;//表示一个有效的字段(方法)的非全限定名(utf8索引)u2descriptor_index;//表示一个有效的字段(方法)的描述符(utf8索引)u2attributes_count;//当前字段(方法)的附加属性的数量attribute_infoattributes[attributes_count];//当前字段(方法)的附加属性}

结合dx的分析内容:

access_flags:public|staticname:mfielddescriptor:iattributes_count:0000

关于attribute的内容其实比较多,在文档中又有将近40页的内容来描述这部分内容。所以这里也不能细说,在理解的时候也只能见招拆招。

属性(attributes)在class文件格式中的classfile结构、field_info结构,method_info(§4.6)结构和code_attribute结构(需要注意,这是个嵌套,因为code_attribute本身也属于attribute)都有使用。

拥有统一的格式:

attribute_info{u2attribute_name_index;u4attribute_length;u1info[attribute_length];}

如果你看到下面有些具体的attribute和上面描述的格式不同,不要诧异,实际上就是info字节拆分,表示不同内容而已!毕竟是地址操作,用点c的思想吧!

attribute_name_index该属性的名字的索引。

attribute_length项的值表示info的长度。

code属性是一个变长属性,位于method_info结构的属性表,保存java虚拟机指令及相关辅助信息。所有java虚拟机实现都必须能够识别code属性。如果方法被声明为native或者abstract类型,那么对应的method_info结构不能有明确的code属性,其它情况下,method_info有必须有明确的code属性(和方法体为空有些不同,即便方法体为空,实际上也有return的虚拟机指令)。

code_attribute{u2attribute_name_index;u4attribute_length;u2max_stack;u2max_locals;u4code_length;u1code[code_length];u2exception_table_length;{u2start_pc;u2end_pc;u2handler_pc;u2catch_type;}exception_table[exception_table_length];u2attributes_count;attribute_infoattributes[attributes_count];}

attribute_name_index固定为constant_utf8_info索引,表示字符串“code”。

max_stack项的值给出了当前方法的操作数栈在运行执行的任何时间点的最大深度。

max_locals项的值给出了分配在当前方法引用的局部变量表中的局部变量个数,包括调用此方法时用于传递参数的局部变量。

code_length项给出了当前方法的code[]数组的字节数(有一些字节码指令的实际长度是超过一个字节的,你会发现code数组不是连续的……),code_length的值必须大于0,即code[]数组不能为空。

code[]数组给出了实现当前方法的java虚拟机字节码。

exception_table_length项的值给出了exception_table[]数组的成员个数量。

exception_table[]数组的每个成员表示code[]数组中的一个异常处理器。

接下去就是code他有的attribute嵌套了,它可以是如下类型linenumbertable,localvariabletable,localvariabletypetable和stackmaptable属性。如果一个java虚拟机实现支持的class文件版本号为50.0或更高,那么它必须正确的识别和读取code属性的属性表出现的stackmaptable属性。

它被调试器用于确定源文件中行号表示的内容在java虚拟机的code[]数组中对应的部分。

它被调试器用于确定方法在执行过程中局部变量的信息。

http://blog.csdn.net/innost/article/details/50377905

http://www.cnblogs.com/lzq198754/p/5780486.html

推荐商标

牛山郡

35类

牛山郡

29类

牛山郡

33类

牛山郡

31类

牛山郡

43类

驼九洛

29类

玑茵专家

03类

小廿轻

43类

分享