ByteCode primer for Java Class Files
It is often stated that to be good at the programming language you code in, one should at least dig and understand one abstraction level lower than the one in which you are coding. For Java, that would be the JVM, which would imply byte code.
In this article we will kick start our adventures in Java byte code. Byte code makes it possible for Java applications to run on varying hardware architectures. Very often we ignore the byte code layer. Understanding it just a little, can go a long way to help us write better Java code.
In this article we will also look at some snippets of Java code and examine their byte code equivalents giving us some insight into what runs under the hood.
1. Introduction
If someone were to ask you, is Java a compiled language or an interpreted language, what would your answer be? Difficult to answer considering it could be seen as both.
Both being the fact that the code we write is initially compiled into JVM byte code to be interpreted by the JVM at run time. At times the code can also occasionally be compiled into machine code, by the JIT compiler , when deemed hot
.
In this article I hope to stimulate some curiosity into what actually happens at a byte code level. For a listing of the different JVM byte codes see here.
2. Technologies used
The example code in this article was built and run using:
- Java 8
- Maven 3.3.9
- STS (3.9.0.RELEASE)
- Ubuntu 16.04
- xxd
3. Setup
For this article, the examples will make use of the javap
and xxd
command line tools so in addition to ensuring that Java and maven are installed we need to ensure the javap
and xxd
tools are accessible on the command line as well.
Confirm Java, Maven and JavaP
$ java -version java version "1.8.0_101" Java(TM) SE Runtime Environment (build 1.8.0_101-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode) $ mvn -version Apache Maven 3.3.9 Maven home: /usr/share/maven Java version: 1.8.0_101, vendor: Oracle Corporation Java home: /home/jean-jay/runtimes/jdk1.8.0_101/jre Default locale: en_ZA, platform encoding: UTF-8 OS name: "linux", version: "4.13.0-26-generic", arch: "amd64", family: "unix" $ javap -version 1.8.0_151 $ xxd -version xxd V1.10 27oct98 by Juergen Weigert
4. Stack Machines
The JVM is a stack machine based virtual machine which helps underscore the design philosophy of the JVM.
Because the JVM was designed to be as platform as possible and initially was meant to have as small a footprint as possible (think applets over the wire), a stack machine was used.
This went a long way to facilitating the design goals of the JVM by virtue of having a small(ish) instruction set (compact to send over the wire) and making no assumptions about the underlying hardware architecture (platform).
4.1 How do they work
The JVM uses a LIFO stack for it’s operands and instructions. Some operands are pushed directly on to the stack whereas others are referred to from the class constant pool or variable array
In any event the next instruction is always the next item to be popped from the stack making it an extremely simple mechanism upon which the JVM operates.
The operand stack is a 32 bit word sized instruction stack meaning each instruction / operand on the stack can be at most 32 bits. This means that operands / instructions that exceed 32 bits (think long
or double
) will take up 2 slots on the stack.
In the above diagram we can see from left to right the sequence of instructions that unfold on the JVM stack when we add two integer values.
iconst_1
and iconst_2
represent the opcodes of pushing the integer value 1
and 2
respectively on the stack. iadd
is the opcode that pops the 2 top values from the stack and adds them and pushes the result back on the stack.
For more detail on how a stack machine works see here but this should be enough to illustrate the concept so that we can continue further with the “nitty gritty” of analyzing our byte code from a class file.
5. Javap and xxd
javap
is a command line tool that comes with the JDK. To be able to view the byte code output / instructions of our java code we will use the javap
tool to disassemble a Java class file.
For more detail on the tool itself and the various command line options see here.
For the purposes of this article, particularly for the inspection of the byte code we will be using the following command line arguments with javap
- -c : Prints disassembled code (byte code)
- -l : Prints line and local variable tables
- -v : Prints verbose / additional information
xxd
is the command line tool to create a HEX dump of a file. Using it is as simple as cat <filename> | xxd
. This will allow us to view the hex output of the class file.
6. Disassembled Output
Before getting stuck into the “nitty gritty” of the byte code, a brief preface of the class file structure is required.
Class file structure
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]; }
A short description of some of these attributes follow:
- In the beginning of the class file we have
magic
which takes up 4 bytes and this is represented by the hex value0xCAFEBABE
which has a very interesting story. - u[0-9] : means
unsigned
and the number represents bytes sou4
would be unsigned 4 bytes. This would mean that we could deduce the index in the class file of where certain structures start and end. For example from32
bits up until48
bits we can find the minor version information for the class file. Inspecting this in a hex editor will reveal it. access_flags
represents the access modifiers of the class itselfthis_class
represents the index in the constant pool which contains the fully qualified class name of this classinterfaces[interfaces_count]
represents an array of indexes in the constant pool of all the interfaces that this class implementsfields[field_count]
represents an array of indexes of in the constant pool representing a description of each field
Below follows the disassembled output (byte code) of the Person
class using the javap
command line tool. To generate this output do the following:
- Download the sample project and navigate to the project root folder once uncompressed.
- Build the project:
mvn clean install package
- Navigate to
<project root folder>/target/classes/com/javacodegeeks/bytecode_primer
and executejavap -c -l -v Person.class
. This will generate the disassembled output of the class as shown below.
Disassembled Output of Person class
Classfile /home/jean-jay/Documents/github-projects/codegeeks/bytecode-primer/target/classes/com/javacodegeeks/bytecode_primer/Person.class Last modified 29 Jan 2018; size 910 bytes MD5 checksum a2f21e47c5dabe433049d1e4c515fdf1 Compiled from "Person.java" public final class com.javacodegeeks.bytecode_primer.Person minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER Constant pool: #1 = Methodref #5.#27 // java/lang/Object."<init>":()V #2 = Fieldref #8.#28 // com/javacodegeeks/bytecode_primer/Person.name:Ljava/lang/String; #3 = Fieldref #8.#29 // com/javacodegeeks/bytecode_primer/Person.age:I #4 = String #30 // [name %s\t : age %d] #5 = Class #31 // java/lang/Object #6 = Methodref #32.#33 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer; #7 = Methodref #34.#35 // java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; #8 = Class #36 // com/javacodegeeks/bytecode_primer/Person #9 = Utf8 name #10 = Utf8 Ljava/lang/String; #11 = Utf8 age #12 = Utf8 I #13 = Utf8 <init> #14 = Utf8 (Ljava/lang/String;I)V #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 LocalVariableTable #18 = Utf8 this #19 = Utf8 Lcom/javacodegeeks/bytecode_primer/Person; #20 = Utf8 getName #21 = Utf8 ()Ljava/lang/String; #22 = Utf8 getAge #23 = Utf8 ()I #24 = Utf8 toString #25 = Utf8 SourceFile #26 = Utf8 Person.java #27 = NameAndType #13:#37 // "<init>":()V #28 = NameAndType #9:#10 // name:Ljava/lang/String; #29 = NameAndType #11:#12 // age:I #30 = Utf8 [name %s\t : age %d] #31 = Utf8 java/lang/Object #32 = Class #38 // java/lang/Integer #33 = NameAndType #39:#40 // valueOf:(I)Ljava/lang/Integer; #34 = Class #41 // java/lang/String #35 = NameAndType #42:#43 // format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; #36 = Utf8 com/javacodegeeks/bytecode_primer/Person #37 = Utf8 ()V #38 = Utf8 java/lang/Integer #39 = Utf8 valueOf #40 = Utf8 (I)Ljava/lang/Integer; #41 = Utf8 java/lang/String #42 = Utf8 format #43 = Utf8 (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; { public com.javacodegeeks.bytecode_primer.Person(java.lang.String, int); descriptor: (Ljava/lang/String;I)V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=3 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: aload_1 6: putfield #2 // Field name:Ljava/lang/String; 9: aload_0 10: iload_2 11: putfield #3 // Field age:I 14: return LineNumberTable: line 8: 0 line 9: 4 line 10: 9 line 11: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/javacodegeeks/bytecode_primer/Person; 0 15 1 name Ljava/lang/String; 0 15 2 age I java.lang.String getName(); descriptor: ()Ljava/lang/String; flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field name:Ljava/lang/String; 4: areturn LineNumberTable: line 14: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/javacodegeeks/bytecode_primer/Person; int getAge(); descriptor: ()I flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #3 // Field age:I 4: ireturn LineNumberTable: line 18: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/javacodegeeks/bytecode_primer/Person; public java.lang.String toString(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC Code: stack=5, locals=1, args_size=1 0: ldc #4 // String [name %s\t : age %d] 2: iconst_2 3: anewarray #5 // class java/lang/Object 6: dup 7: iconst_0 8: aload_0 9: getfield #2 // Field name:Ljava/lang/String; 12: aastore 13: dup 14: iconst_1 15: aload_0 16: getfield #3 // Field age:I 19: invokestatic #6 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 22: aastore 23: invokestatic #7 // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; 26: areturn LineNumberTable: line 23: 0 LocalVariableTable: Start Length Slot Name Signature 0 27 0 this Lcom/javacodegeeks/bytecode_primer/Person; } SourceFile: "Person.java"
The disassembled output above can be grouped into 3 main sections, the header section (containing version information), the constant pool section and the methods.
6.1 Header section
In the header section we have the version information which indicates the class format version among other things. Thus the JVM can support the particular class file format if it’s version falls in the range minor:major of the JVM.
6.2 Constant pool
Per class / type a constant pool is managed containing data typically too big to store in byte code’s itself or that is used in multiple places in a class.
Sample byte code to understand constant pool lookup (construct an object)
0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: aload_0 5: aload_1 6: putfield #2 // Field name:Ljava/lang/String; 9: aload_0 10: iload_2 11: putfield #3 // Field age:I 14: return LineNumberTable: line 8: 0 line 9: 4 line 10: 9 line 11: 14 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/javacodegeeks/bytecode_primer/Person; 0 15 1 name Ljava/lang/String; 0 15 2 age I
- line 1: an opcode that loads a reference onto the stack. The index part
0
represents the index in the local variable table for the method / constructor (explained later), the first index is always the instance type itself. Remember the code being executed belongs to a method or constructor of the class. The local variable table is a table containing all the method / constructor arguments that were passed during invocation. Soaload_0
means load the first argument on the stack, the first argument in a variable table is always thethis
reference. - line 2: represents an instance initialization method, it contains an index reference to the constant pool
#1
and also consumes the top of the stack (from line 1 the reference type being constructed –this
/Person
) . If we navigate to index 1 in the constant pool above we see it refers to a method reference(#1)
on classjava.lang.Object
(#5)
which is the constructor (<init>)(#27)
. So what happens is that we pass the reference from line 1 (Person
) as argument for the instance initialization, meaning we construct aPerson
instance. - line 3: we again load the
Person
reference from the local variable table on to the stack. (aload_0
) - line 4 we load the second argument in the local variable on to the stack, the
String
name
variable - line 5:
putfield
pops the two top values from the stack (currentlyPerson
and the name value) and then stores it in the reference denoted by the constant pool index#2
- line 6: we again load the
Person
reference from the local variable table on to the stack. (aload_0
) - line 7: we load the third argument in the local variable table, the age value, this is an integer hence the opcode is slightly different,
iload_2
- line 8:
putfield
pops the top two values from the stack and stores the value ofage
in the constant pool reference at index#3
. - line 9: because this is a constructor, hence no return value, we return
void
hence the opcodereturn
Using this method of chaining the look ups we are able to infer the exact value (eventually) of a constant pool reference.
6.3 Method section
In the method sections we list the various methods / constructors for the class. Most of this was touched on in the section above. One feature provided for in the method section(s) is the LineNumberTable
which is really just an aid to debuggers to help identify the line number in the code.
7. Hex Dump
Below follows a hex dump of the same class file from the sample project. To create a hex dump we use the xxd
command line tool. This can be done by executing the following:
- Navigate to the project root folder and build the project
mvn clean install package
- Navigate to the
<project root folder>/target/classes/com/javacodegeeks/bytecode_primer/
- Execute the following:
cat Person.class | xxd
Hex dump of Person class
00000000: cafe babe 0000 0034 002c 0a00 0500 1b09 .......4.,...... 00000010: 0008 001c 0900 0800 1d08 001e 0700 1f0a ................ 00000020: 0020 0021 0a00 2200 2307 0024 0100 046e . .!..".#..$...n 00000030: 616d 6501 0012 4c6a 6176 612f 6c61 6e67 ame...Ljava/lang 00000040: 2f53 7472 696e 673b 0100 0361 6765 0100 /String;...age.. 00000050: 0149 0100 063c 696e 6974 3e01 0016 284c .I......(L 00000060: 6a61 7661 2f6c 616e 672f 5374 7269 6e67 java/lang/String 00000070: 3b49 2956 0100 0443 6f64 6501 000f 4c69 ;I)V...Code...Li 00000080: 6e65 4e75 6d62 6572 5461 626c 6501 0012 neNumberTable... 00000090: 4c6f 6361 6c56 6172 6961 626c 6554 6162 LocalVariableTab 000000a0: 6c65 0100 0474 6869 7301 002a 4c63 6f6d le...this..*Lcom 000000b0: 2f6a 6176 6163 6f64 6567 6565 6b73 2f62 /javacodegeeks/b 000000c0: 7974 6563 6f64 655f 7072 696d 6572 2f50 ytecode_primer/P 000000d0: 6572 736f 6e3b 0100 0767 6574 4e61 6d65 erson;...getName 000000e0: 0100 1428 294c 6a61 7661 2f6c 616e 672f ...()Ljava/lang/ 000000f0: 5374 7269 6e67 3b01 0006 6765 7441 6765 String;...getAge 00000100: 0100 0328 2949 0100 0874 6f53 7472 696e ...()I...toStrin 00000110: 6701 000a 536f 7572 6365 4669 6c65 0100 g...SourceFile.. 00000120: 0b50 6572 736f 6e2e 6a61 7661 0c00 0d00 .Person.java.... 00000130: 250c 0009 000a 0c00 0b00 0c01 0013 5b6e %.............[n 00000140: 616d 6520 2573 0920 3a20 6167 6520 2564 ame %s. : age %d 00000150: 5d01 0010 6a61 7661 2f6c 616e 672f 4f62 ]...java/lang/Ob 00000160: 6a65 6374 0700 260c 0027 0028 0700 290c ject..&..'.(..). 00000170: 002a 002b 0100 2863 6f6d 2f6a 6176 6163 .*.+..(com/javac 00000180: 6f64 6567 6565 6b73 2f62 7974 6563 6f64 odegeeks/bytecod 00000190: 655f 7072 696d 6572 2f50 6572 736f 6e01 e_primer/Person. 000001a0: 0003 2829 5601 0011 6a61 7661 2f6c 616e ..()V...java/lan 000001b0: 672f 496e 7465 6765 7201 0007 7661 6c75 g/Integer...valu 000001c0: 654f 6601 0016 2849 294c 6a61 7661 2f6c eOf...(I)Ljava/l 000001d0: 616e 672f 496e 7465 6765 723b 0100 106a ang/Integer;...j 000001e0: 6176 612f 6c61 6e67 2f53 7472 696e 6701 ava/lang/String. 000001f0: 0006 666f 726d 6174 0100 3928 4c6a 6176 ..format..9(Ljav 00000200: 612f 6c61 6e67 2f53 7472 696e 673b 5b4c a/lang/String;[L 00000210: 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 java/lang/Object 00000220: 3b29 4c6a 6176 612f 6c61 6e67 2f53 7472 ;)Ljava/lang/Str 00000230: 696e 673b 0031 0008 0005 0000 0002 0012 ing;.1.......... 00000240: 0009 000a 0000 0012 000b 000c 0000 0004 ................ 00000250: 0001 000d 000e 0001 000f 0000 0059 0002 .............Y.. 00000260: 0003 0000 000f 2ab7 0001 2a2b b500 022a ......*...*+...* 00000270: 1cb5 0003 b100 0000 0200 1000 0000 1200 ................ 00000280: 0400 0000 0800 0400 0900 0900 0a00 0e00 ................ 00000290: 0b00 1100 0000 2000 0300 0000 0f00 1200 ...... ......... 000002a0: 1300 0000 0000 0f00 0900 0a00 0100 0000 ................ 000002b0: 0f00 0b00 0c00 0200 0000 1400 1500 0100 ................ 000002c0: 0f00 0000 2f00 0100 0100 0000 052a b400 ..../........*.. 000002d0: 02b0 0000 0002 0010 0000 0006 0001 0000 ................ 000002e0: 000e 0011 0000 000c 0001 0000 0005 0012 ................ 000002f0: 0013 0000 0000 0016 0017 0001 000f 0000 ................ 00000300: 002f 0001 0001 0000 0005 2ab4 0003 ac00 ./........*..... 00000310: 0000 0200 1000 0000 0600 0100 0000 1200 ................ 00000320: 1100 0000 0c00 0100 0000 0500 1200 1300 ................ 00000330: 0000 0100 1800 1500 0100 0f00 0000 4500 ..............E. 00000340: 0500 0100 0000 1b12 0405 bd00 0559 032a .............Y.* 00000350: b400 0253 5904 2ab4 0003 b800 0653 b800 ...SY.*......S.. 00000360: 07b0 0000 0002 0010 0000 0006 0001 0000 ................ 00000370: 0017 0011 0000 000c 0001 0000 001b 0012 ................ 00000380: 0013 0000 0001 0019 0000 0002 001a ..............
8. Summary
In this article we covered what byte code is and what role it plays in making your Java program work.
We briefly touched upon the JVM as a stack machine and how it functions when executing our code instructions. To make the concept more tangible we also analyzed the byte code generated by a simple class in the sample project.
We analysed these byte code listings to understand how it is interpreted by the JVM.
9. Download the Source Code
This was a ByteCode primer for Java Class Files.
You can download the full source code of this example here: ByteCode primer for Java Class Files