Core Java

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.

 

Stack Machine Functioning

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 1and 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 value 0xCAFEBABE which has a very interesting story.
  • u[0-9] : means unsigned and the number represents bytes so u4 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 from 32 bits up until 48 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 itself
  • this_class represents the index in the constant pool which contains the fully qualified class name of this class
  • interfaces[interfaces_count] represents an array of indexes in the constant pool of all the interfaces that this class implements
  • fields[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:

  1. Download the sample project and navigate to the project root folder once uncompressed.
  2. Build the project: mvn clean install package
  3. Navigate to <project root folder>/target/classes/com/javacodegeeks/bytecode_primer and execute javap -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. So aload_0 means load the first argument on the stack, the first argument in a variable table is always the this 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 class java.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 a Person 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 (currently Person 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 of age in the constant pool reference at index #3.
  • line 9: because this is a constructor, hence no return value, we return void hence the opcode return

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:

  1. Navigate to the project root folder and build the project mvn clean install package
  2. Navigate to the <project root folder>/target/classes/com/javacodegeeks/bytecode_primer/
  3. 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.

Download
You can download the full source code of this example here: ByteCode primer for Java Class Files

JJ

Jean-Jay Vester graduated from the Cape Peninsula University of Technology, Cape Town, in 2001 and has spent most of his career developing Java backend systems for small to large sized companies both sides of the equator. He has an abundance of experience and knowledge in many varied Java frameworks and has also acquired some systems knowledge along the way. Recently he has started developing his JavaScript skill set specifically targeting Angularjs and also bridged that skill to the backend with Nodejs.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button