我们编译一个显示“ Hello World”的简单程序,并逐步浏览其结构
对于那些不完全了解字节码的形式以及JVM如何使用字节码的人(例如,至少是最简单的指令(关于其存在的知识)),我认为这篇文章不会为您提供足够的信息。
实际上,这并不困难。 使用JDK中的javap
工具并考虑反汇编的代码就足够了。
我们将开始分析JVM字节码的结构
对此非常有用的书是官方的JVM规范-oracle上的Java虚拟机规范
首先,创建一个简单的程序:
public class Main { public static void main(String ... args) { System.out.println("Hello World"); } }
与javac Main.java
团队进行编译,然后进行反汇编
javap -c -v Main
主类
Classfile /C:/Users/Arthur/playground/java/jvm/Main.class Last modified 26.10.2019; size 413 bytes MD5 checksum 6449121a3bb611fee394e4f322401ee1 Compiled from "Main.java" public class Main minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello World #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Main #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 Main.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 Hello World #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 Main #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V { public Main(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String...); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS Code: stack=2, locals=1, args_size=1 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 LineNumberTable: line 4: 0 line 5: 8 } SourceFile: "Main.java"
这只是一个字节码表示形式,比原始字节码更容易让人看到,但看起来却有所不同:
cafe babe 0000 0034 001d 0a00 0600 0f09 0010 0011 0800 120a 0013 0014 0700 1507 0016 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 046d 6169 6e01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 0100 0a53 6f75 7263 6546 696c 6501 0009 4d61 696e 2e6a 6176 610c 0007 0008 0700 170c 0018 0019 0100 0b48 656c 6c6f 2057 6f72 6c64 0700 1a0c 001b 001c 0100 044d 6169 6e01 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0100 106a 6176 612f 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 7401 0015 4c6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 3b01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d01 0007 7072 696e 746c 6e01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5600 2100 0500 0600 0000 0000 0200 0100 0700 0800 0100 0900 0000 1d00 0100 0100 0000 052a b700 01b1 0000 0001 000a 0000 0006 0001 0000 0001 0089 000b 000c 0001 0009 0000 0025 0002 0001 0000 0009 b200 0212 03b6 0004 b100 0000 0100 0a00 0000 0a00 0200 0000 0400 0800 0500 0100 0d00 0000 0200 0e
(您可以通过Sublime Text打开.class
文件,该Sublime Text指示File-> Encoding with Encoding-> Hexademical)
我们将使用此代码。
但是首先,我们需要对其进行格式化,以免混淆其所在位置,而字节码实际上具有非常严格的结构:
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
您可以在JVM规范第4.1章ClassFile结构中找到它
这里的一切都很简单-尺寸单位在左侧,说明在右侧。
我们将以十六进制分析字节码,其中每个数字占用4位,因此,对于两个字节-4位和对于四个字节-8位。
魔术
magic是一个标识我们类格式的值。 它等于0xCAFEBABE
,它具有自己的创建历史 。
minor_version,major_version
这些是您的class
文件的版本。 如果我们调用major_version
M和minor_version
m,我们得到的class
文件的版本为Mm
现在,我将立即从程序“ Hello World”中给出示例,以了解其用法:
cafe babe -- magic 0000 -- minor_version 0034 -- major_version
我们可以在反汇编的代码中看到它,但是在十进制数系统中已经可以看到它:
... public class Main minor version: 0 major version: 52 flags: ACC_PUBLIC, ...
constant_pool_count
此处显示了常量池中的变量数。 同时,如果您决定以纯字节码编写代码,那么您肯定需要监视其值,因为如果您指定了错误的值,则整个程序将陷入困境(选中!)。
另外,不要忘记您应该在___ + 1
写入___ + 1
of_variables_ ___ + 1
总计,我们得到:
cafe babe -- magic 0000 0034 -- version 001d -- constant_pool_count
constant_pool []
常量池中的每种类型的变量都有其自己的结构:
cp_info { u1 tag; u1 info[]; }
一切都需要按顺序进行。 首先,我们读取tag
以找出变量的类型,然后根据该变量的类型,查看其后续值具有info[]
可在表4.3常量池标签规范中找到带有标签的表 。
实际上,这是平板电脑:
如前所述,每种类型的常数都有其自己的结构。
例如,这里是CONSTANT_Class
结构:
CONSTANT_Class_info { u1 tag; u2 name_index; }
字段和方法结构:
CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
在此重要的是要注意,不同的结构可能具有不同的长度。
考虑我们的代码的一部分:
cafe babe 0000 0034 001d -- constant_pool_count 0a00 0600 0f09 0010 0011 0800 12 ...
因此,我们看一下常量的结构,发现第一个字节是为常量类型保留的。 在这里我们看到0a
(10)-因此,它是CONSTANT_Methodref
我们看一下它的结构:
CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
在标记使用一个字节之后,我们需要name_and_type_index
4个字节用于class_index
和name_and_type_index
cafe babe 0000 0034 001d -- constant_pool_count 0a 0006 000f -- CONSTANT_Methodref 0900 1000 1108 0012 ...
好吧,我们找到了常量池的值之一。 来吧 我们看一下09
表示CONSTANT_Fieldref
类型
我们得到:
cafe babe 0000 0034 001d -- constant_pool_count 0a 0006 000f -- CONSTANT_Methodref 09 0010 0011 -- CONSTANT_Fieldref 08 0012 ...
您可能会认为大多数类型具有相同的形状,但事实并非如此。
例如,以下类型的结构看起来像CONSTANT_String
:
CONSTANT_String_info { u1 tag; u2 string_index; }
所有这些结构都可以在第4.4章“常量池”中找到。
现在,让我们看看info
本身内部的类型是什么意思。
属于*_index
模式的方法通常包含常量池表中的地址。 例如, class_index
表示CONSTANT_Class_info
类型的值, string_index
表示CONSTANT_Class_info
类型的string_index
我们可以在反汇编代码中看到这一点:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18
0a 0006 000f -- CONSTANT_Methodref 09 0010 0011 -- CONSTANT_Fieldref 08 0012 -- CONSTANT_String
您还可以突出显示数字和字符串的表示形式。
您可以从第4.4.4章开始阅读有关数字的表示形式,但是由于Hello World程序中尚未包含数字,因此现在我们仅分析行
实际上,这是该行的显示方式:
CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
例如,我们的Hello World:
01 -- tag 000b -- length 48 65 6c 6c 6f 20 57 6f 72 6c 64 -- bytes[length] // H ello W orld
解析整个字节码常量池,我们得到:
整个常量池 -- [Constant Pool] -- methodref 0a 0006 000f -- fieldref 09 0010 0011 -- string 08 0012 -- methodref 0a 0013 0014 -- Class 07 0015 -- Class 07 0016 -- Utf8 01 0006 3c 69 6e 69 74 3e -- Utf8 01 0003 28 29 56 -- Utf8 01 0004 43 6f 64 65 -- Utf8 01 000f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 -- Utf8 01 0004 6d 61 69 6e -- Utf8 01 0016 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 -- Utf8 01 000a 53 6f 75 72 63 65 46 69 6c 65 -- Utf8 01 0009 4d 61 69 6e 2e 6a 61 76 61 -- NameAndType 0c 0007 0008 -- Class 07 0017 -- NameAndType 0c 0018 0019 -- Utf8 01 000b 48 65 6c 6c 6f 20 57 6f 72 6c 64 -- Class 07 001a -- NameAndType 0c 001b 001c -- Utf8 01 0004 4d 61 69 6e -- Utf8 01 0010 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 -- Utf8 01 0010 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d -- Utf8 01 0003 6f 75 74 -- Utf8 01 0015 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b -- Utf8 01 0013 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d -- Utf8 01 0007 70 72 69 6e 74 6c 6e -- Utf8 01 0015 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 -- [Constant Pool END]
另外,我们可以将其与反汇编代码进行比较:
Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello World #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Main #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 Main.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 Hello World #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 Main #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V
从而检查所有内容是否匹配,因为实际上javap
只是处理该字节码,并以格式化的形式显示给我们。
需要常量池来进行指示。 例如:
public Main(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // 1 4: return
有关常量池中所有类型的更多信息,请参见第4.4章“常量池”。
在ClassFile结构中更进一步
access_flags
这是修饰符属性的位掩码。
this_class
必须包含this
的地址。 在我们的例子中,它位于地址5:
Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello World #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Main #6 = Class #22 // java/lang/Object ...
应当注意,此变量的结构必须符合CONSTANT_Class_info
超类
类祖先地址。 在我们的情况下,该值位于地址#6
。 好吧,还需要CONSTANT_Class_info
值结构
这些类的名称在CONSTANT_Utf8_info
常量的结构中定义。 如果看一下单元格#21
和#22
,我们将看到:
... #21 = Utf8 Main #22 = Utf8 java/lang/Object ...
也就是说,在这些单元格中,该结构的name_index
表示为:
CONSTANT_Class_info { u1 tag; u2 name_index; }
interfaces_count,fields_count
它们不在我们的程序中,因此它们的值将等于0000,并且就不会再有fields[]
, interfaces[]
后续值。
阅读更多4.1 ClassFile结构
Methods_count
方法数量。 尽管在代码中我们在类中看到了一个方法,但实际上有两个方法。 除了main
方法之外,还有一个默认构造函数。 因此,在我们的案例中,它们的数量是两个。
方法[]
每个元素必须符合4.6章方法中描述的method_info
结构。
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
在我们的字节码(格式化的,带有注释)中,它看起来像这样:
-- [methods] -- public Main(); 0001 --access_flags 0007 -- name_index 0008 -- descriptor_index 0001 -- attributes_count -- attribute_info 0009 -- attribute_name_index (Code) 0000 001d - attribute_length 0001 -- max_stack 0001 -- max_locals 0000 0005 -- code_length 2a b7 00 01 b1 -- code[] 0000 -- exception_table_length 0001 -- attributes_count; 000a -- attribute_name_index 0000 0006 -- attribute_length 00 01 00 00 00 01 -- public static void main(java.lang.String...); 0089 --access_flags 000b -- name_index 000c -- descriptor_index 0001 -- attributes_count -- attribute_info 0009 -- attribute_name_index (Code) 0000 0025 -- attribute_length 0002 -- max_stack 0001 -- max_locals 0000 0009 -- code_length b2 00 02 12 03 b6 00 04 b1 -- code[] 0000 -- exception_table_length 0001 -- attributes_count 000a -- attribute_name_index 0000 000a -- attribute_length 00 02 00 00 00 04 00 08 00 05 -- [methods END]
让我们更详细地分析方法的结构:
access_flags
修饰符蒙版。 表4.5方法访问和属性标志
从字节码中可以看到,在public Main();
方法中public Main();
(构造函数)为掩码0001
,表示ACC_PUBLIC
。
现在,让我们尝试自己组装main
方法。 这是他所拥有的:
- 公开-ACC_PUBLIC-0x0001
- 静态-ACC_STATIC-0x0008
- 字符串... args-ACC_VARARGS-0x0080
我们收集掩码:0x0001 + 0x0008 + 0x0080 = 0x0089
。 这样我们获得了access_flag
顺便说一下,ACC_VARARGS在这里是可选的,因为如果我们
使用String [] args代替String ... args,则此标志将不会
name_index
常量池中的方法名称地址( CONSTANT_Utf8_info
)。 此处需要注意的重要一点是,构造函数名称不是Main,而是位于单元#7中的<init>
。
在第2.9章特殊方法中了解有关<init>
和<clinit>
更多信息。
描述符索引
粗略地说,这是指向方法句柄的地址。 该描述符包含返回值的类型及其签名的类型。
此外,JVM使用解释的缩写:
通常,它看起来像这样:
( ParameterDescriptor* ) ReturnDescriptor
例如,以下方法:
Object method(int i, double d, Thread t) {..}
可以表示为
(IDLjava/lang/Thread;)Ljava/lang/Object
实际上, I
是int
, D
是double
,并且是Ljava/lang/Thread;
class来自标准java.lang
库的Thread
。
接下来,还有一些属性也具有自己的结构。
但首先,与往常一样,其attributes_count
计数
然后,属性本身具有第4.7章“属性”中描述的结构。
attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }
attribute_name_index
指定属性名称。 在我们的例子中,这两种方法都有一个Code
。 属性是一个单独的大主题,您甚至可以在其中按规范创建自己的属性。 但就目前而言,我们应该知道attribute_name_index
仅指向具有CONSTANT_Utf8_info
结构的常量池中的地址。
attribute_length
包含属性的长度,不包括attribute_name_index
和attribute_length
资讯
接下来,我们将使用Code
结构,因为在attribute_name_index
的值中,我们指向了Code
常量池中的值。
阅读更多: 第4.7.3章,代码属性
这是它的结构:
Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }
max_stack
在我看来,由于前缀max,此属性的名称可能会引起误解。 实际上,这是完成操作所需的最小堆栈大小。 好吧,这个名称具有逻辑意义,也就是说在操作期间将达到的最大堆栈大小。
简而言之,JVM将为操作数堆栈分配空间。 您可以在此处指定一个大于必要值的值,但是在此属性中定义的值小于必要值将导致错误。
关于堆栈主题,您可以阅读“ 在Java世界的上下文中的堆栈和堆上 ”或“ JVM内部 ”
max_locals
局部变量的最大大小
您可以在JVM核心的Mastering Java Bytecode中或在同一JVM Internals中熟悉局部变量。
code_length
该方法内部将执行的代码的大小
代码[]
每个代码都指向一些指令。 optcode
和命令与助记符的相关性表可以在Wikipedia- Java字节码指令列表中找到,也可以在本书结尾处的规范中找到。
例如,使用我们的构造函数:
-- public Main(); 0001 --access_flags 0007 -- name_index 0008 -- descriptor_index 0001 -- attributes_count -- attribute_info 0009 -- attribute_name_index (Code) 0000 001d - attribute_length 00 01 -- max_stack 00 01 -- max_locals 00 00 00 05 -- code_length 2a b7 00 01 b1 -- code[] 0000 -- exception_table_length 0001 -- attributes_count; 00 0a -- attribute_name_index 0000 0006 -- attribute_length 00 01 00 00 00 01
在这里我们可以找到我们的代码:
2a b7 00 01 b1
我们在表中查找命令并进行比较:
2a - aload_0 b7 0001 - invokespecial #1 b1 - return
这些命令的描述也可以在这里找到: 第4.10.1.9章。 类型检查说明
exception_table_length
指定exception_table中的元素数。 我们没有异常钩子,因此我们不会解析它。 但是您还可以阅读第4.7.3章“代码属性”
exception_table []
它具有以下结构:
{ u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; }
为了简化,您需要指定handler_pc
将要处理的代码的开始,结束( start_pc
, end_pc
)和catch_type
异常catch_type
attribute_count
Code
的属性数
属性[]
分析器或调试器经常使用属性。
字节码工具
这不是与本文相关的主题,但仍与之间接相关。
有很多使用字节码的工具。 在这里,我想回顾一下Apache Commons的字节码工程库 (BCEL)。
首先,使用它我们可以获得一些字节码属性:
完整代码清单 import org.apache.bcel.classfile.*; import org.apache.commons.codec.binary.Hex; import java.io.*; public class ClassParserExample { public static void main(String... args) throws IOException, ClassNotFoundException { // read file from resources/complied/ClassA.class InputStream inputStream = ClassParserExample.class.getResourceAsStream("/compiled/ClassA.class"); if (inputStream == null) throw new FileNotFoundException(); ClassParser parser = new ClassParser(inputStream, "ClassA.class"); JavaClass clazz = parser.parse(); final String HEX_BYTECODE = getHex(clazz.getBytes()); System.out.println("Hex bytecode: "); System.out.println(HEX_BYTECODE); System.out.println(); final String MINOR_VER = getHex(clazz.getMinor()); final String MAJOR_VER = getHex(clazz.getMajor()); final String CONSTANT_POOL = getHex(clazz.getConstantPool().getConstantPool()); final String ACCESS_FLAGS = getHex(clazz.getAccessFlags()); final String THIS_CLASS = getHex(clazz.getClassName().getBytes()); final String SUPER_CLASS = getHex(clazz.getSuperClass().getBytes()); final String INTERFACES = getHex(clazz.getInterfaces()); final String FIELDS = getHex(clazz.getFields()); final String METHODS = getHex(clazz.getMethods()); final String ATTRIBUTES = getHex(clazz.getAttributes()); System.out.println( "minor: " + MINOR_VER ); // 0 System.out.println( "major: " + MAJOR_VER ); // 34 System.out.println( "constant pool: " + CONSTANT_POOL); // not correctly System.out.println( "access flags: " + ACCESS_FLAGS ); // 21 System.out.println( "this class: " + THIS_CLASS ); System.out.println( "super class: " + SUPER_CLASS ); // Object System.out.println( "interfaces: " + INTERFACES ); // <empty> System.out.println( "fields: " + FIELDS ); // <empty> System.out.println( "methods: " + METHODS ); // one method: psvm hello world System.out.println( "attributes: " + ATTRIBUTES ); // 536f7572636546696c65 - I think it's instructions for Java tools } private static String getHex(byte[] bytes){ return Hex.encodeHexString(bytes); } private static String getHex(int intNum){ return Integer.toHexString(intNum); } private static String getHex(Constant[] constants){ if (constants == null) return null; StringBuilder sb = new StringBuilder(); for (Constant c : constants){ if (c == null) continue; sb.append(getHex(c.getTag())).append(" "); } return sb.toString(); } private static String getHex(JavaClass[] clazzes){ if (clazzes == null) return null; StringBuilder sb = new StringBuilder(); for (JavaClass c : clazzes){ sb.append(getHex(c.getClassName().getBytes())).append(" "); } return sb.toString(); } private static String getHex(Field[] fields){ if (fields == null) return null; StringBuilder sb = new StringBuilder(); for (Field c : fields){ sb.append(getHex(c.getName().getBytes())).append(" "); } return sb.toString(); } private static String getHex(Method[] methods){ if (methods == null) return null; StringBuilder sb = new StringBuilder(); for (Method c : methods){ sb.append(getHex(c.getName().getBytes())).append(" "); } return sb.toString(); } private static String getHex(Attribute[] attributes){ if (attributes == null) return null; StringBuilder sb = new StringBuilder(); for (Attribute c : attributes){ sb.append(getHex(c.getName().getBytes())).append(" "); } return sb.toString(); } } /* Class A: public class ClassA { public static void main(String[] args) { System.out.println("Hello world"); } } */ /* Class A bytecode: cafe babe 0000 0034 0022 0a00 0600 1409 0015 0016 0800 170a 0018 0019 0700 1a07 001b 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 124c 6f63 616c 5661 7269 6162 6c65 5461 626c 6501 0004 7468 6973 0100 1d4c 636f 6d2f 6170 706c 6f69 6478 7878 2f70 6172 7365 2f43 6c61 7373 413b 0100 046d 6169 6e01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 0100 0461 7267 7301 0013 5b4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b01 000a 536f 7572 6365 4669 6c65 0100 0b43 6c61 7373 412e 6a61 7661 0c00 0700 0807 001c 0c00 1d00 1e01 000b 4865 6c6c 6f20 776f 726c 6407 001f 0c00 2000 2101 001b 636f 6d2f 6170 706c 6f69 6478 7878 2f70 6172 7365 2f43 6c61 7373 4101 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0100 106a 6176 612f 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 7401 0015 4c6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 3b01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d01 0007 7072 696e 746c 6e01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5600 2100 0500 0600 0000 0000 0200 0100 0700 0800 0100 0900 0000 2f00 0100 0100 0000 052a b700 01b1 0000 0002 000a 0000 0006 0001 0000 0006 000b 0000 000c 0001 0000 0005 000c 000d 0000 0009 000e 000f 0001 0009 0000 0037 0002 0001 0000 0009 b200 0212 03b6 0004 b100 0000 0200 0a00 0000 0a00 0200 0000 0800 0800 0900 0b00 0000 0c00 0100 0000 0900 1000 1100 0000 0100 1200 0000 0200 13 */ /* Assembled code: Classfile /C:/java/BCEL/src/main/resources/compiled/ClassA.class Last modified 08.12.2019; size 563 bytes MD5 checksum bcd0198f6764a1dc2f3967fef701452e Compiled from "ClassA.java" public class com.apploidxxx.parse.ClassA minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#20 // java/lang/Object."<init>":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // Hello world #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // com/apploidxxx/parse/ClassA #6 = Class #27 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/apploidxxx/parse/ClassA; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 ClassA.java #20 = NameAndType #7:#8 // "<init>":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 Hello world #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 com/apploidxxx/parse/ClassA #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V { public com.apploidxxx.parse.ClassA(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/apploidxxx/parse/ClassA; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 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 LineNumberTable: line 8: 0 line 9: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; } SourceFile: "ClassA.java */
, (, Jasmin) -.
, Jasmin . , , , JVM -.
:
Hello World .bytecode 52.0 .source Main.j .class public Main .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 2 .limit locals 2 getstatic java/lang/System/out Ljava/io/PrintStream; ldc "Hello world!" invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V return .end method
; ClassCreating.j .bytecode 52.0 .source ClassCreating.java .class public ClassCreating .super java/lang/Object .method public <init>()V .limit stack 1 .limit locals 1 .line 1 0: aload_0 1: invokespecial java/lang/Object/<init>()V 4: return .end method .method public static main([Ljava/lang/String;)V ; Flag ACC_VARARGS set, see JVM spec .limit stack 2 .limit locals 3 .line 3 0: new java/lang/String 3: dup 4: invokespecial java/lang/String/<init>()V 7: astore_1 .line 4 8: new ClassCreating 11: dup 12: invokespecial ClassCreating/<init>()V 15: astore_2 .line 5 16: aload_2 17: invokevirtual ClassCreating/sayHello()V .line 6 20: return .end method .method public sayHello()V .limit stack 2 .limit locals 1 .line 9 0: getstatic java/lang/System/out Ljava/io/PrintStream; 3: ldc "Hello, User!" 5: invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V .line 10 8: return .end method
Hello World
- : gist.github
.
二手文献