Fundamentals 35 min read

Exploring Java Serviceability Agent (SA): JVM Memory Model, JIT, and Low‑Level Debugging Tools

This article provides a comprehensive, English‑language walkthrough of Java Serviceability Agent (SA), covering how to obtain and analyze JVM internal structures, the memory model and Linux process layout, the oop/klass split, JIT compilation mechanics, and the implementation of tools such as jmap and jstack, all illustrated with code snippets and diagrams.

Qunar Tech Salon
Qunar Tech Salon
Qunar Tech Salon
Exploring Java Serviceability Agent (SA): JVM Memory Model, JIT, and Low‑Level Debugging Tools

1 Expected Gains

Master the technique of using SA to probe JVM internals.

Understand the JVM memory model, execution engine, and Linux process memory layout.

Grasp the oop/klass split model of the JVM.

Learn the working principle of JIT and its source‑code analysis.

Analyze the bytecode instruction execution flow.

Explore the implementation of jmap, jstack and other low‑level tools.

Comprehend the overall operation of Java SA.

2 Background

In a micro‑service architecture, rapid business iteration leads to a proliferation of services and code, resulting in bloated systems where many modules become candidates for removal. During the 1024 Programmer's Day hackathon, a "system slimming tool" based on Java SA won the championship by enabling zero‑intrusion detection of removable code. This article records the author's exploration of Java SA, aiming to help readers understand JVM runtime data and internal mechanisms.

3 Source Code Preparation

3.1 Overview of Required Sources

The main HotSpot code is written in C++ with a small amount of C and assembly. Fortunately, SA and other agents are implemented in Java, making source reading easier. The relevant sources are:

<dependency>
    <groupId>jdk</groupId>
    <artifactId>sa-jdi</artifactId>
    <version>1.8</version>
</dependency>

Additional tools such as jmap and jstack reside in jdk/src/share/classes/sun/tools/. Object, class, and method representations are in hotspot/src/share/, while Linux process and virtual‑memory structures are in linux-2.6.0/include/linux/.

3.2 Downloading Sources

OpenJDK source: http://hg.openjdk.java.net/jdk8

Linux kernel source: https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/

4 Java SA Overview

SA (Serviceability Agent) is a private Sun component developed by HotSpot engineers that allows reading Java objects and internal VM data structures from a running Java process or core dump. It differs from the attach mechanism in that SA reads OS‑level memory directly without executing any code inside the target VM.

Execution Mechanism

Direct OS‑level memory read

Impact on Target Process

No

JDK Version Differences

Significant

attach

Client/Server socket interaction; may affect target

Understanding SA requires knowledge of the JVM memory model, the oop/klass split, and JIT compilation.

5 JVM Memory Model and Linux Process Layout

5.1 JVM Memory Model and Execution Engine

Figure 1 (omitted) shows the JVM memory model: shared heap, metaspace, per‑thread Java stack, native stack, and program counter. Execution can be interpreted or compiled, with garbage collection occurring during runtime.

The model abstracts away OS and CPU specifics, providing Java's cross‑platform guarantee. However, the JVM isolates Java processes from the OS, making low‑level inspection challenging—exactly what SA enables.

5.2 Linux Process Memory Layout

In Linux each process is represented by a task_struct. The mm_struct member holds memory information. A simplified excerpt:

struct mm_struct {
    struct vm_area_struct *mmap;       // linked list of VMA
    struct rb_root mm_rb;               // red‑black tree of VMA
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long start_brk, brk, start_stack;
    ...
}

Figure 2 (omitted) illustrates the typical Linux process layout: text segment, data segment, BSS, heap, stack, and memory‑mapped region (shared libraries, etc.). The JVM's libjvm.so is loaded into the memory‑mapped region, and SA reads symbols from this library to locate JVM internal structures.

6 JVM oop/klass Split Model

6.1 Model Overview

All Java objects are represented by two C++ structures: a Klass (metadata) and an OopDesc (instance data). The following example demonstrates the creation of a Student object and the resulting JVM structures.

package com.qunar.sa;

public class TargetProcess {
    public static void main(String[] args) throws Exception {
        int id = 1;
        Student student = new Student();
        student.setId(id);
        Thread.sleep(10000000L * 1000);
    }
    static class Student {
        private static int type = 10;
        private int id;
        public void setId(int id) { this.id = id; }
        void JITTest1(){ System.out.println(this.id); }
        void JITTest2(){ System.out.println(this.id); }
        void JITTest3(){ System.out.println(this.id); }
    }
}

After setId executes, the JVM creates:

A InstanceKlass object (metadata) stored in the metaspace.

An instanceOopDesc object (instance) stored on the heap, with a pointer from the stack variable.

The relevant C++ definition (excerpt from oop.hpp) is:

volatile markOop  _mark;
union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
} _metadata;

7 JIT Compilation Mechanism

7.1 Method Call Information Storage

Method call counters are stored in the Klass of each class. The counter encodes call count, compilation flag, and state in a 32‑bit integer.

7.2 Hot‑Compilation Triggers

Two triggers exist:

Invocation counter : when the call count exceeds a threshold, the method is compiled asynchronously (standard compilation).

Back‑edge counter : loops generate back‑edge events; once the back‑edge counter exceeds its threshold, OSR (On‑Stack Replacement) compilation occurs.

7.3 Hotness Decay

If a method does not reach the compilation threshold within a certain period, its counter is halved (decay). The decay logic ensures the counter never reaches zero for methods that have been executed at least once.

private: unsigned int _counter; // [count|carry|state]
enum State { wait_for_nothing, wait_for_compile, number_of_states };

inline void InvocationCounter::decay() {
    int c = count();
    int new_count = c >> 1;
    if (c > 0 && new_count == 0) new_count = 1;
    set(state(), new_count);
}

7.4 Back‑edge Counter Statistics

The bytecode interpreter increments the back‑edge counter on each goto execution. When the OSR threshold is reached, an OSR compilation request is issued.

CASE(_goto): {
    int16_t offset = (int16_t)Bytes::get_Java_u2(pc + 1);
    address branch_pc = pc;
    UPDATE_PC(offset);
    DO_BACKEDGE_CHECKS(offset, branch_pc);
    CONTINUE;
}

#define DO_BACKEDGE_CHECKS(skip, branch_pc) \
    mcs->backedge_counter()->increment(); \
    if (do_OSR) do_OSR = mcs->backedge_counter()->reached_InvocationLimit();

7.5 Accessing JIT Data via SA (Code Way)

The following simplified SA program collects method invocation counts and compiled‑method information for the Student class.

package com.qunar.sa;

public class SAProcess {
    public static void main(String[] args) throws ParseException {
        int pid = 20408;
        HotSpotAgent agent = new HotSpotAgent();
        agent.attach(pid);
        try {
            Set<MethodDefinition> methodResult = new HashSet<>();
            VM.getVM().getSystemDictionary().allClassesDo(new InvocationCounterVisitor(methodResult));
            System.out.println("SA method info: " + JacksonSupport.toJson(methodResult));
            Set<MethodDefinition> compiledMethodResult = new HashSet<>();
            VM.getVM().getCodeCache().iterate(new CompiledMethodVisitor(compiledMethodResult));
            System.out.println("SA compiled data: " + JacksonSupport.toJson(compiledMethodResult));
        } finally { agent.detach(); }
    }
    // Visitor implementations omitted for brevity
}

Sample output shows invocation counts (after right‑shifting three bits) and which methods have been JIT‑compiled.

8 Implementation of jmap, jstack and Similar Tools

8.1 attach‑Based Mechanism

Tools send a SIGQUIT to the target JVM, which spawns an Attach Listener thread. A socket connection is established, the command (e.g., inspectheap) is sent, the target processes it, and the result is returned over the socket.

8.2 SA‑Based Mechanism

SA reads the target process memory directly, without any cooperation from the target JVM. For jmap -heap, SA locates the global variable gHotSpotVMTypes via the symbol table of libjvm.so, computes its absolute address using the VMA base from /proc/[pid]/maps, and reads the data with ptrace.

9 Java SA Runtime Mechanism

9.1 Steps to Retrieve Data

Identify the offset of a global variable (e.g., gHotSpotVMTypes) from the readelf symbol table.

Obtain the base address of libjvm.so in the target process via /proc/[pid]/maps.

Add offset to base to get the absolute virtual address.

Use ptrace to read the variable's value.

Interpret the raw memory using the VMStructEntry and VMTypeEntry structures, which describe the layout of all JVM objects.

Key structures:

typedef struct {
    const char* typeName;   // e.g., SystemDictionary
    const char* fieldName;  // e.g., _dictionary
    const char* typeString; // e.g., Dictionary*
    int32_t isStatic;
    uint64_t offset;        // used for non‑static fields
    void* address;          // absolute address for static fields
} VMStructEntry;

typedef struct {
    const char* typeName;
    const char* superclassName;
    int32_t isOopType;
    int32_t isIntegerType;
    int32_t isUnsigned;
    uint64_t size;
} VMTypeEntry;

SA APIs (e.g., sun.JVM.hotspot.HotSpotTypeDataBase) use these entries to locate fields such as SystemDictionary._dictionary and retrieve their values.

References

Usenix paper on Java SA: https://static.usenix.org/event/JVM01/fullpapers/russell/russell.html OpenJDK SA documentation: http://openjdk.java.net/groups/hotspot/docs/Serviceability.html Reading symbol tables from shared libraries: https://blog.csdn.net/raintungli/article/details/7289639

END

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaJVMJITMemory ModelLow‑Level ToolsServiceability Agent
Qunar Tech Salon
Written by

Qunar Tech Salon

Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.