JVM Bytecode Notes: The Class File Structure

Summary0x01 Preface Every note in this series is based on Understanding JVM Bytecode in Depth by Zhang Ya and is intended only as a personal study summary. Readers studying Java security or JVM bytecode are strongly encouraged to purchase and read the official book. 0x02 Class-File Structure Java is cross-platform, although the JVM itself is platform-specific. JVM implementations hide those platform differences by compiling source code into platform-independent bytecode, so…

jvm.jpeg

0x01 Preface

Every note in this series is based on Understanding JVM Bytecode in Depth by Zhang Ya and is intended only as a personal study summary.

Readers studying Java security or JVM bytecode are strongly encouraged to purchase and read the official book.

0x02 Class-File Structure

Java is cross-platform, although the JVM itself is platform-specific. JVM implementations hide those differences by compiling source code into platform-independent bytecode. Source code therefore need not be compiled separately into a different native executable for every platform. This is the purpose of Java bytecode.

A class file consists of ten parts:

  • Magic number
  • Version numbers (minor and major version)
  • Constant pool
  • Access flags
  • Class index (this_class)
  • Superclass index
  • Interface-table index
  • Field table
  • Method table
  • Attribute table

A mnemonic can help us remember

My Very Cute Animal Truns Savage In full Moon Areas.

My adorable pet becomes irritable during a full moon.

#### 1. Magic Number

A magic number identifies a file's type from its contents. The class-file magic number is0xcafebabe. Before loading a class file, the virtual machine checks these four bytes and throws a if they do not matchjava.lang.ClassFormatErrorexception.

Java's creator James Gosling once wrote that a band regularly played at a restaurant he frequented. After the band's singer died, they called the place "Cafe Dead." Gosling was then designing file encodings and needed two magic numbers: one for object persistence and one for class files. Both shared the prefix "cafe." He chose cafedead for persistent-object files and cafebabe for class files.

#### 2. Version Numbers (Minor and Major Version)

The four bytes after the magic number represent the minor version and major version, two bytes each.

For example:CA FE BA BE 00 00 00 34

The major version is therefore:0x34=4x1+3x16=52

3. Constant Pool

The constant pool is the most complex data structure in a class file.

For a common operand such as 0, the JVM places the operandEmbeddedinto the bytecode, whereas if it isString constantorLarger integer, the class file stores the operands in the constant pool and looks them up by constant-pool index when needed.

Its data structure is illustrated below:

C
struct{
  u2					constant_pool_count;
  cp_info			constant_poll[constant_pool_count-1];
}

The constant pool has two parts: its size, cp_info_count, and the collection of constant-pool entries, cp_info.

Constant-pool size (cp_info_count)

The constant-pool size occupies two bytes. If the size is n, the valid pool indices are 1 through n−1. Index 0 is reserved for special cases.

Constant-pool entry (cp_info)

The constant pool contains at most n−1 entries. Long and double constants each consume two index positions, so if either type is present, the actual number of entries is smaller than n−1.

The cp_info data structure is illustrated below:

C
cp_info{
  u1 tag;
  u2 info[];
}

The first byte of each constant-pool entry is its type tag; the following bytes contain the entry's actual data.

The Java Virtual Machine defines 14 constant-pool tag types. Their names begin with CONSTANT and end with info.

Constant type Value Description
CONSTANT_Utf8_info 1 UTF-8-encoded string
CONSTANT_Integer_info 3 represents an int constant; boolean, byte, short, and char
CONSTANT_Float_info 4 represents a float value
CONSTANT_Long_info 5 Long integer literal
CONSTANT_Double_info 6 Double-precision literal
CONSTANT_Class_info 7 represents a class or interface
CONSTANT_String_info 8 Constant object of type java.lang.String
CONSTANT_Fieldref_info 9 Field-information table
CONSTANT_Methodref_info 10 method
CONSTANT_InterfaceMethodref_info 11 Interface method
CONSTANT_NameAndType_info 12 Name-and-type table
CONSTANT_MethodHandle_info 15 Method-handle table
CONSTANT_MethodType_info 16 Method-type table
CONSTANT_InvokeDynamic_info 18 Dynamic method call site

① CONSTANT_Utf8_info

CONSTANT_Utf8_info stores an MUTF-8-encoded string and has the following structure:

C
CONSTANT_Utf8_info {
   u1 tag;  // always 1
   u2 length;  	// the length of the bytes array
   u1 bytes[length];  	// a length-byte array encoded in MUTF-8
}

The author explains the subtle differences between MUTF-8 and UTF-8, which also reveals why class-file strings use MUTF-8 instead of standard UTF-8.

The book notes that MUTF-8 is broadly similar to UTF-8 but not compatible with it. There are two differences:

First, MUTF-8 encodes the null character U+0000 as two bytes, 0xC0 0x80, whereas standard UTF-8 uses the single byte 0x00. Languages such as C treat a null byte as a string terminator—the basis of so-called %00 truncation. MUTF-8's representation prevents null bytes from appearing within strings and therefore avoids accidental truncation when C code processes them.

Second, MUTF-8 uses only the one-, two-, and three-byte forms of UTF-8, not the four-byte form. Java represents characters above U+FFFF with two characters called a surrogate pair. For example, the face-with-tears-of-joy emoji has the surrogate pair\ud83d\ude02

The first point is straightforward. Understanding the second requires knowing the one-, two-, three-, and four-byte forms of UTF-8, which the following section briefly explains.

  • One-byte

Range:0x0001 ~ 0x007F, while UTF-8 usesOne bytenotation:

0000 0001 ~ 0000 007F -> 0xxxxxxx

That is, English letters have the same ASCII and UTF-8 encodings.

  • Two-byte

Range:0x0080 ~ 0x07FF, while UTF-8 usesTwo bytesnotation:

0000 0080 ~ 0000 07FF -> 110xxxxx 10xxxxxx

That is, remove from the first byte110 remove it, and from the second byte remove 10 remove it, then take the remainingxto form the new two-byte data.

  • Three-byte

Range:0x0800 ~ 0xFFFF, while UTF-8 usesThree bytesrepresents:

0000 0800 ~ 0000 FFFF -> 1110xxxx 10xxxxxx 10xxxxxx

That is, remove from the first byte 1110remove it, and from the second byte remove10remove it, and from the third byte remove10remove it, then take the remainingxto form the new three-byte data.

  • Four-byte

Range:0001 0000 ~ 0010 FFFF, while UTF-8 usesFour bytesrepresents:

0001 0000 ~ 0010 FFFF -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

That is, remove from the first byte 11110remove it, and from the second byte remove10remove it, and from the third byte remove10remove it, and from the fourth byte remove10remove it, then take the remainingxto form the new four-byte data.

  • Example:

has the Unicode encoding0x673A(0110 0111 0011 1010)

Because0x673Afalls in the three-byte range and is therefore encoded in three bytes:

1.png

The value substituted for x is:011 001110 0111010

produces the UTF-8 encoding0xE69CBA

Returning to the earlier example, the face-with-tears-of-joy emoji has the surrogate pair\ud83d\ude02, namely:

D83D DE02. If we define:

JAVA
public final String y = "\ud83d\ude02";

Opening the compiled class file shows the face-with-tears-of-joy emoji represented as:

01 00 06 ED A0 BD ED B8 82

01represents the constant item's tag,00 06represents the length of the byte array—the following six bytesED A0 BD ED B8 82represents the face-with-tears-of-joy emoji

ED A0 BDcorresponding binary value is11101101 10100000 10111101. Because this is a three-byte encoding, remove from the first byte 1110, and from the second byte10, and from the third byte10, leaving1101100000111101, which in hexadecimal is:0xD83D

Similarly,ED B8 82The same calculation gives0xDE02. As a surrogate pair it is represented as:\ud83d\ude02

Notably, my research also found that Java serialization uses MUTF-8.java.io.DataInputandjava.io.DataOutputinterfaces respectively definereadUTF()andwriteUTF()method can read and write MUTF-8 strings.

② CONSTANT_Integer_info

represents an int constant. Boolean, byte, short, and char variables are also represented as Integer entries in the constant pool.

③ CONSTANT_Float_info

represents a float constant.

④ CONSTANT_Long_info

represents a long constant

⑤ CONSTANT_Double_info

represents a double constant

⑥ CONSTANT_Class_info

represents a class or interface.

⑦ CONSTANT_String_info

represents java.lang.Stringconstant object of type, which differs fromCONSTANT_Utf8_infodiffers in thatCONSTANT_Utf8_infostores the actual string content, whileCONSTANT_String_infodoes not contain the string itself, only a reference to a in the constant poolCONSTANT_Utf8_infoindex of the constant type.

⑧ CONSTANT_Fieldref_info

points toCONSTANT_Class_infoconstant-pool index identifying the class that owns the method.

⑨ CONSTANT_Methodref_info

describes a method.

⑩ CONSTANT_InterfaceMethodref_info

points toCONSTANT_NameAndType_infoconstant-pool index identifying the method name, parameters, and return type.

⑪ CONSTANT_NameAndType_info

represents a field or method.

⑫ CONSTANT_MethodHandle_info、CONSTANT_MethodType_info、CONSTANT_InvokeDynamic_info

CONSTANT_MethodHandle_inforepresents a method handle. Operations such as reading a static or instance field, invoking a method, or invoking a constructor are converted into handle references.

CONSTANT_MethodType_inforepresents a method type.

CONSTANT_InvokeDynamic_inforepresents dynamic call-site reference information.

The author notes that these three constant-pool types were introduced in JDK 1.7 to better support dynamic-language calls. My own searches found little especially useful information. Some writers say the three entries are used only in highly unusual circumstances and are almost never generated in class files; others describe their structures and values in detail. See:https://juejin.cn/post/6844903950777319432#heading-17

In the book, the author principally discussesCONSTANT_InvokeDynamic_info. This type is primarily used by invokedynamicinstruction supplies a bootstrap method. Its structure is:

C
CONSTANT_InvokeDynmic_info{
  u1 tag;
  u2 bootstrap_method_attr_index;
  u2 name_and_type_index;
}

The first parttaghas the fixed value 18; the second partbootstrap_method_attr_indexpoints to the bootstrap-method tablebootstrap_method[]array index; the third partname_and_type_indexpoints to the in the indexed constant-pool classCONSTANT_NameAndType_infoindex for d, representing the method descriptor.

A concrete example makes this easier to understand; see:https://blog.csdn.net/zxhoo/article/details/38387141

#### 4. Access Flags

Access flags identify whether a class is final, abstract, public, and so on. They occupy two bytes, providing 16 possible bits, of which eight are currently used, as shown below.

2.png

The complete meanings of the access flags appear in the following table:

Flag name Flag value Meaning Applies to
ACC_PUBLIC 0x0001 public type All types
ACC_FINAL 0x0010 final type class
ACC_SUPER 0x0020 Uses the new invokespecial semantics Classes and interfaces
ACC_INTERFACE 0x0200 interface type interface
ACC_ABSTRACT 0x0400 abstract type Classes and interfaces
ACC_SYNTHETIC 0x1000 The class is not generated from user code All types
ACC_ANNOTATION 0x2000 annotation type annotation
ACC_ENUM 0x4000 enum type Enum

Class access flags can be combined. For example, a class may have the access flags0x0021(ACC_SUPER|ACC_PUBLIC), indicating a public class. Combinations are subject to constraints, however; for example,ACC_PUBLICcannot be combined withACC_PRIVATEset simultaneously,ACC_FINALandACC_ABSTRACTcannot be set simultaneously either, because that would violate Java's fundamental semantics.

The relevant source can be found in the javac source undercom.sun.tools.javac.comp.Check.javain the source.

#### 5. Class Index, Superclass Index, and Interface-Table Index

These three structures determine class inheritance relationships. In them,this classrepresents the class index,super classrepresents the direct-superclass index,interfacesdescribes which interfaces the class implements. These three values normally index the constant pool, each representing a class, interface, superclass, or related item.

6. Field Table

Field table (field) stores fields defined in the class, both static and instance fields. Its structure can be represented by the following pseudocode:

C
{
	u2			fields_count;
	field_info		fields[fileds_count];
}

In the preceding structure,fields_count is used to represent field count,fields represents the field collection, containing fileds_count entries, each field represented by field_infostructure. Let us therefore examine filed_info structure:

C
filed_info{
		u2			access_flags;
		u2			name_index;
		u2			descriptor_index;
		u2			attributes_count;
		attribute_info		attributes[attributes_count];
}

As shown above, we can see filed_info has a four-part structure. The first part is access_flags, represents field access flags and can, for example, distinguish whether a field ispublicprivateprotectedstaticand other types; the second part isname_indexidentifies the field name and points to a string constant in the constant pool; the third partdescriptor_indexis the field-descriptor index and likewise points to a string constant in the constant pool; the final part is composed ofattributes_countandattribute_infocombine to represent the attribute count and attribute collection, respectively.

The first part contains access flags like those for a class, but field access flags are more varied, with nine types in total.

Flag name Flag value Meaning
ACC_PUBLIC 0x0001 public type
ACC_PRIVATE 0x0002 private type
ACC_PROTECTED 0x0004 protected type
ACC_STATIC 0x0008 static type
ACC_FINAL 0x0010 final type
ACC_VOLATILE 0x0040 volatile type, used to address memory-visibility issues
ACC_TRANSIENT 0x0080 transient type; fields marked transient are not serialized by default
ACC_SYNTHETIC 0x1000 The class is generated by the compiler, not user code
ACC_ENUM 0x4000 enum type

For example, suppose the class defines the field

JAVA
public static final int DEFAULT_SIZE = 128

After compilation DEFAULT_SIZE The access-flag value stored for the field in the class file is 0x0019

This value is composed of ACC_PUBLIC | ACC_STATIC | ACC_FINAL combine to indicate a public static final variable.

A field has the following default in-memory representation:

3.png

Then public static final is represented as:

4.png

Therefore the binary 0001 1001 converted to hexadecimal is 0x0019 , which is also the origin of this marker value

As with class access flags, field flags cannot be combined arbitrarily. For example, ACC_FINAL and ACC_VOLATILE cannot be set at the same time

The field descriptor in the third part also deserves closer study.

A field descriptor represents a field's type. For an int field, a class file stores the compact letter I rather than the string "int." Field descriptors therefore fall into three categories according to the field type:

  • Primitive types: byte, int, char, float, and so on use a single character. J represents long and B represents byte. Readers familiar with serialization will recognize the same notation used for primitive fields there.
  • Reference types use L; notation. To prevent ambiguity between consecutive reference-type descriptors, every reference descriptor ends with a ; as the terminator. For example, the descriptor of the String type is Ljava/lang/String;
  • The JVM uses a leading [ notation for array types, such as int[] type has the descriptor [I , while the descriptor for the String[] array is [Ljava/lang/String;, while array descriptors simply add several [ only, for example Object[][][] type has the descriptor [[[Ljava/lang/Object;This should look familiar: fastjson versions 1.2.25–1.2.41 were exploited by adding an initial L and a trailing semicolon to a class name to bypass every blacklist entry.

7. Method Table

The method table serves a role similar to the field table, storing methods defined by the class. It is also a variable-length structure:

TEXT
{
	u2		methods_count;
	method_info		methods[methods_count];
}

methods_count gives the number of methods, while methods is a collection containing that many entries, each represented by a method_info structure.

The method_info structure is:

TEXT
{
	u2		access_flags;
	u2		name_index;
	u2		descriptor_index;
	u2		attributes_count;
	attribute_info	attributes[attributes_count];
}

method_info has four parts: access_flags contains the method access flags, name_index identifies the method name,

descriptor_index is the method-descriptor index, attributes_count is the number of method-related attributes, and attribute_info is their collection. The structure is illustrated below:

5.png

Method access flags are more varied than class and field access flags, with 12 types in total:

Flag name Flag value Meaning
ACC_PUBLIC 0x0001 public type
ACC_PRIVATE 0x0002 private type
ACC_PROTECTED 0x0004 protected type
ACC_STATIC 0x0008 static type
ACC_FINAL 0x0010 final type
ACC_SYNCHRONIZED 0x0020 synchronized type
ACC_BRIDGE 0x0040 bridge method generated by the compiler
ACC_VARARGS 0x0080 Method accepts variable-length arguments, such as String... args
ACC_NATIVE 0x0100 native type
ACC_ABSTRACT 0x0400 abstract type
ACC_STRICT 0x0800 strictfp type, indicating precise IEEE-754 floating-point semantics; rarely used
ACC_SYNTHETIC 0x1000 indicates a method generated automatically by the compiler rather than from user code

For example, consider this method:

JAVA
private static synchronized void foo(){
}

In the generated class file, the access-flag value of foo is 0x002a

This value is composed of ACC_PRIVATE | ACC_STATIC | ACC_SYNCHRONIZED combine to indicate a private static synchronized method

A method has the following default in-memory representation:

6.png

Then the private static synchronized method is represented as:

7.png

Therefore the binary 0010 1010 converted to hexadecimal is 0x002a , which is also the origin of this marker value

As with field access flags, not every combination of method access flags is valid.

Finally, method descriptors deserve mention. They closely resemble the field descriptors covered earlier and use the following format:

(arg1Type arg2Type arg3Type ... )returnType

For example, the methodObject foo(int i,double d, Thread t)has the descriptor (IDLjava/lang/Thread;)Ljava/lang/Object; Here,I indicates that the first parameter i has type int,D indicates that the second parameter d has type double,Ljava/java/Thread; indicates that the third parameter t has type Thread,Ljava/lang/Object; indicates an Object return type, as shown below:

8.png

8. Attribute Table

The attribute table is the final part of a class file. Attributes can appear in fields and methods as well as at the top-level class-file structure. Attribute types are flexible, and different virtual-machine implementations may define their own. The attribute-table structure is:

TEXT
{
	u2		attributes_count;
	attribute_info attributes[attributes_count];
}

Like other structures, an attribute table uses two bytes for attributes_count, followed by a collection of attribute entries that can be viewed as an array. Each item is an attribute_info structure, and the array length is attributes_count. The attribute_info structure is:

TEXT
{
	u2		attribute_name_index;
	u4		attribute_length;
	u1 		info[attribute_length];
}

attribute_name_index points into the constant pool and identifies the attribute name. The remaining two parts give the length of the info array and the contents of its byte array.

The virtual machine predefines more than twenty attributes. The book introduces two: ConstantValue and Code.

The book explains that ConstantValue appears in field_info and represents the initial value of a static variable.

The book describes Code as one of the most important components of a class file because it contains method bytecode. Except for native and abstract methods, every method has exactly one Code attribute. Code appears only in the method table, and its structure is:

TEXT
Code_attribute{
	u2		attribute_name_index;
	u4		attribute_length;
	u2		max_stack;
	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];
}

attribute_name_index identifies the attribute name, attribute_length gives the value length, and max_stack is the maximum depth of the operand stack. At runtime the VM uses max_stack to allocate the operand-stack depth in a stack frame. Instructions that push increase stack; instructions that pop decrease it. The highest value reached is max_stack. Most changes are 1, but LONG- and DOUBLE-related pushes add 2, while VOID contributes 0.

max_locals gives the size of the local-variable table, not the sum of every local variable declared in the method. When a local scope ends, its slots can be reused by variables declared later.

code_length and code describe the bytecode. code_length stores the bytecode length in four bytes. Although this could nominally represent up to 2^32−1, the JVM limits a method body to 65,535 bytes of bytecode. The code array stores the Java bytecode instructions compiled from the method body. There is no need to memorize every instruction; consult an opcode table as needed. See:https://www.cnblogs.com/longjee/p/8675771.html

exception_table_length and exception_table describe the exception table inside the code. start_pc, end_pc, and handler_pc are indices into the code byte array. start_pc and end_pc define the half-open bytecode range [start_pc, end_pc) covered by the handler. handler_pc gives the starting offset of the exception handler in code—the point to which execution jumps after an exception is caught.

catch_type identifies the exception type handled by a catch entry. It occupies two bytes and points to a CONSTANT_Class_info item in the constant pool. A catch_type of 0 handles any exception.

If an exception occurs while the JVM executes bytecode in [start_pc, end_pc), and the exception is an instance of catch_type or one of its subclasses, execution jumps to handler_pc in the code byte array.

The book also gives the Code attribute structure, which is fairly intuitive. Interested readers can consult it directly.

At the end of Chapter 1, the author introduces techniques for inspecting class files with javap. Many online resources cover this, for example:https://blog.csdn.net/jkli52051315/article/details/83943473

0x03 Conclusion

Chapter 1 mainly introduces the internal structure of class files, and I learned a great deal from it. There is no such thing as learning too much foundational material.

I will continue reading the book and sharing my study notes.