SkyWalking JVM Observability Best Practices¶
Introduction¶
JVM is a specification for computing devices. It is a virtual computer, short for Java Virtual Machine. Java is a highly abstract language that provides features such as automatic memory management, hence the need for this abstraction layer known as JVM. JVM runs on top of the operating system to execute Java bytecode, enabling Java to achieve cross-platform compatibility.
Below, we will briefly introduce the JVM memory structure and threads, then use SkyWalking to collect JVM Metrics data and achieve observability through Guance.
JVM Memory Structure¶
The Java compiler targets only the JVM, generating bytecode files understandable by the JVM. The class loader in the JVM loads these bytecodes, which are then executed by the JVM execution engine. Throughout the class loading process, JVM uses a segment of space to store data and related information, which is commonly referred to as JVM memory.
According to the JVM specification, JVM memory is divided into:
-
Program Counter Used to record the address of the next JVM instruction to be executed.
-
Virtual Machine Stack Each stack consists of multiple frames (Frame), corresponding to the memory used during each method call. It stores information such as local variable tables, operand stacks, constant pool references. From method invocation to completion, it corresponds to the push and pop processes of a frame in the Java virtual machine stack. The size of the virtual machine stack can be set using -Xss.
-
Native Method Stack Similar to the virtual machine stack in function and characteristics, but serves native methods.
-
Method Area The method area is a JVM specification, with PermGen and Metaspace being two implementation methods. In JDK8 and later, the original PermGen data has been split into heap and Metaspace. Metaspace stores class metadata, while static variables and string pools are placed in the heap.
-
Heap The heap is shared by all threads and primarily stores object instances and arrays. It can reside in physically non-contiguous spaces but must be logically contiguous. The heap is divided into Young Generation and Old Generation. The Young Generation further splits into Eden and Survivor regions. Survivor regions consist of FromSpace and ToSpace. Eden takes up most of the capacity, while Survivor regions take up less, with a default ratio of 8:1:1. If there is no available memory in the Java heap to complete an instance allocation and the heap cannot expand further, the Java virtual machine will throw an OutOfMemoryError exception.
-
Runtime Constant Pool The runtime constant pool is part of the method area, serving as a table from which the virtual machine instructions find class names, method names, parameter types, etc.
-
Direct Memory Direct memory is not part of the runtime data area or defined by the Java virtual machine specification. It is limited by the total physical memory rather than the Java heap size. Direct memory can be specified using -XX:MaxDirectMemorySize. Allocating direct memory consumes more performance compared to heap memory, but direct memory I/O read/write performance is better. Exhausting direct memory throws an OutOfMemoryError exception.
Threads¶
Java has two types of threads: User Threads and Daemon Threads.
- User Threads can be understood as system working threads that perform business operations required by the application.
- Daemon Threads are special threads that run in the background to provide system-level services, such as garbage collection threads.
Threads in the JVM have the following states:
- New (NEW): Thread state before execution starts.
- Runnable (RUNNABLE): Thread state during execution.
- Blocked (BLOCKED): Waiting for a monitor lock.
- Waiting (WAITING): Indefinitely waiting for another thread to perform a specific operation.
- Timed Waiting (TIMED_WAITING): Waiting for another thread to perform a specific operation within a time limit.
- Terminated (TERMINATED): Thread is in a terminated state.
Performance Metrics¶
Metric | Description | Data Type | Unit |
---|---|---|---|
class_loaded_count |
Loaded class count. | int | count |
class_total_loaded_count |
Total loaded class count. | int | count |
class_total_unloaded_class_count |
Total unloaded class count. | int | count |
cpu_usage_percent |
CPU usage percentile | float | percent |
gc_phrase_old/new_count |
GC old or new count. | int | count |
heap/stack_committed |
Heap or stack committed amount of memory. | int | count |
heap/stack_init |
Heap or stack initialized amount of memory. | int | count |
heap/stack_max |
Heap or stack max amount of memory. | int | count |
heap/stack_used |
Heap or stack used amount of memory. | int | count |
pool_*_committed |
Committed amount of memory in various pools (code_cache_usage,newgen_usage,oldgen_usage, survivor_usage,permgen_usage,metaspace_usage). |
int | count |
pool_*_init |
Initialized amount of memory in various pools (code_cache_usage,newgen_usage,oldgen_usage, survivor_usage,permgen_usage,metaspace_usage). |
int | count |
pool_*_max |
Max amount of memory in various pools (code_cache_usage,newgen_usage,oldgen_usage, survivor_usage,permgen_usage,metaspace_usage). |
int | count |
pool_*_used |
Used amount of memory in various pools (code_cache_usage,newgen_usage,oldgen_usage, survivor_usage,permgen_usage,metaspace_usage). |
int | count |
thread_blocked_state_count |
Blocked state thread count | int | count |
thread_daemon_count |
Daemon thread count. | int | count |
thread_live_count |
Live thread count. | int | count |
thread_peak_count |
Peak thread count. | int | count |
thread_runnable_state_count |
Runnable state thread count. | int | count |
thread_time_waiting_state_count |
Time waiting state thread count. | int | count |
thread_waiting_state_count |
Waiting state thread count. | int | count |
Prerequisites¶
- Install DataKit 1.4.17+
Configuration Steps¶
1 Enable Collector¶
Enter the host where DataKit is installed and copy the sample file.
If the DataKit deployment and the target JVM's Jar are not on the same host, you need to modify the localhost
in address = "localhost:11800"
in skywalking.conf
to the IP address of the host where the Jar resides.
In this case, the Jar and DataKit are on the same host, so using localhost is sufficient to report JVM metrics without modifying the sample file.
2 Deploy Application¶
Download the skywalking-demo project, open it with Idea, click 「package」, and generate the skywalking-user-service.jar
file.
Download Skywalking, extract it, and copy the agent directory to the same directory as skywalking-user-service.jar
.
Run the following command to start the application.
java -javaagent:agent/skywalking-agent.jar \
-Dskywalking.agent.service_name=skywalking-user \
-Dskywalking.collector.backend_service=localhost:11800 \
-jar skywalking-user-service.jar
3 JVM Observability¶
Log in to 「 Guance」 - 「Scenes」, input "JVM", select 「JVM Skywalking Monitoring View」, and click 「Confirm」.
Click on the newly created 「JVM Skywalking Monitoring View」 to begin monitoring.