Spark Profiler — Debugging & Performance Analysis Guide
A practical reference for using spark to investigate performance issues on Minecraft servers, including CPU usage, memory allocations, lag spikes, garbage collection, and Netty thread bottlenecks.
This guide focuses on useful commands, when to use them, and how to interpret the results.
Quick Troubleshooting Guide
Not sure which profiling mode to use? Find your issue below and jump to the relevant section.
| Problem / Symptom | What to investigate | Section |
|---|---|---|
| High CPU usage | Expensive methods, CPU-intensive tasks, overloaded threads | |
| High GC pressure / excessive object creation | Memory allocation rates and expensive allocation paths | |
| Occasional TPS drops / lag spikes | Individual slow ticks and expensive main-thread operations | |
| High ping / network-related lag (possibly affecting only some players) | Individual overloaded Netty threads, uneven workloads, packet processing, plugin-related network overhead | |
| High RAM usage / suspected memory leak | Hosting panel memory vs JVM heap, G1 Old Gen, retained objects, heap dumps | |
| Not sure how long to profile | Profiling duration, workload reproduction, best practices | |
| Confusing flame graphs / misleading percentages | Inclusive vs self time, allocation rates, combined threads | |
| Need a command quickly | Copy-paste profiling commands | |
| Need better visualization / plugin attribution | Userscript, client-side analysis, Netty attribution, sleeping-time cleanup |
General recommendation: Start by identifying the symptoms you're experiencing rather than running every profiling mode. Make sure to collect profiles while the issue is occurring, and compare different profiling modes when necessary.
If you want a fairly comprehensive overview of your server's performance, I recommend collecting these three profiles:
- Main-thread overview
/spark profiler start --timeout 300
Provides a general overview of what the main server thread is doing, helping you understand where tick time is being spent and spot potential bottlenecks. - CPU usage (all threads)
/spark profiler start --thread * --ignore-sleeping --timeout 300
Helps identify CPU-intensive operations across all threads, while excluding sleeping time. - Memory allocations (all threads)
/spark profiler start --thread * --alloc --timeout 300
Identifies methods responsible for memory allocations, helping detect excessive object creation and potential GC pressure.
Together, these profiles provide a solid starting point for investigating most server performance issues. For more specific problems, such as intermittent lag spikes, thread blocking, or Netty-related latency, refer to the dedicated sections below.
1. CPU Usage Profiling
To investigate CPU usage across all threads, excluding sleeping time:
/spark profiler start --thread * --ignore-sleeping --timeout 300
When to use:
- Investigating high CPU usage.
- Finding expensive methods or code paths.
- Identifying overloaded asynchronous threads.
- Understanding where CPU time is being spent.
Important: --ignore-sleeping
This option excludes sleeping/waiting time from the profiling results, helping you focus on active execution.
However, this can hide problematic behavior if your code sleeps, waits, or blocks in places where it shouldn't.
For this reason, I recommend also collecting a profile without --ignore-sleeping:
/spark profiler start --thread * --timeout 300
Comparing both reports can help distinguish expensive computations from excessive waiting, blocking, or synchronization issues.
Tip: Use Profiler Utils (userscript) or Spark Profile Tools (website) to analyze these reports more effectively.
2. Memory Allocation Profiling
To identify excessive memory allocations across all threads:
/spark profiler start --thread * --alloc --timeout 300
When to use:
- Investigating excessive garbage collection activity.
- Finding methods responsible for high allocation rates.
- Identifying unnecessary object creation.
- Diagnosing memory-related performance issues.
Notes:
--allocenables allocation profiling instead of CPU profiling.--thread *includes all threads.--timeout 300automatically stops profiling after 300 seconds.
Important: High allocation rates do not necessarily indicate a memory leak. Short-lived objects can generate substantial GC pressure without increasing retained heap usage.
If you suspect a memory leak, investigate retained objects rather than relying exclusively on allocation profiling.
3. Main-Thread Lag Spikes
To investigate slow server ticks:
/spark profiler start --only-ticks-over 200 --timeout 300
This captures profiling samples associated with ticks exceeding 200 milliseconds.
When to use:
- Investigating intermittent TPS drops.
- Finding the cause of occasional server freezes.
- Diagnosing expensive operations occurring during specific ticks.
- Investigating lag spikes that don't appear in average performance metrics.
Adjusting the threshold
The --only-ticks-over value is expressed in milliseconds.
For particularly severe lag spikes, such as individual ticks taking more than one second:
/spark profiler start --only-ticks-over 1000 --timeout 300
Choose a threshold appropriate for the issue you're investigating.
A lower threshold captures more slow ticks, while a higher threshold focuses on particularly severe spikes.
Tip: If the server generally performs well but occasionally freezes, tick-based profiling is often more useful than collecting a general CPU profile.
4. Netty Thread Profiling (High Ping / Network Issues)
When players experience unusually high ping or network-related delays, profiling Netty threads can help identify expensive packet processing or overloaded network workers.
Separate Netty threads
/spark profiler start --ignore-sleeping --not-combined --thread Netty.* --regex --timeout 300
Recommended when:
- Investigating individual Netty threads.
- Checking whether a particular Netty worker is overloaded.
- Comparing workloads across network threads.
- Identifying uneven distribution of packet processing.
Why --not-combined is especially useful for Netty
Netty uses multiple event-loop threads, with each thread handling network operations for a subset of player connections.
In some cases, a single Netty thread may become overloaded while the others remain relatively idle. This can result in high ping or delayed packet processing only for players whose connections are handled by that particular thread, while other players experience no issues.
This is why --not-combined is particularly important when investigating Netty-related problems:
- Without
--not-combined: Samples from multiple Netty threads are merged, potentially hiding an individual overloaded thread. - With
--not-combined: Each Netty thread can be analyzed separately, making it easier to identify uneven workloads and isolated bottlenecks.
Recommendation: When investigating high ping, especially if only some players are affected, start with --not-combined. A combined profile can still be useful afterward for understanding overall Netty resource usage.
Combined Netty threads
/spark profiler start --ignore-sleeping --thread Netty.* --regex --timeout 300
Recommended when:
- Investigating overall Netty CPU usage.
- Finding expensive operations across all Netty workers.
- Getting a general overview of network processing overhead.
Notes:
--thread Netty.*selects matching Netty threads.--regexenables regular-expression matching for thread names.--ignore-sleepingexcludes sleeping/waiting time.
Important: Netty resource attribution
Netty uses shared event loops and asynchronous packet processing, which can make it difficult to determine which plugin is responsible for resource consumption from a standard flame graph.
I recommend using Spark Profile Tools, which provides plugin-level resource attribution and can make Netty profiling results significantly easier to interpret.
Also remember that high ping doesn't necessarily indicate a Netty bottleneck. Main-thread stalls, network conditions, and other factors can contribute to latency.
5. Memory Usage and Potential Memory Leaks
"My server is using too much RAM!" — Is it a memory leak?
High RAM usage in a hosting panel such as Pterodactyl does not necessarily mean your server has a memory leak. The panel generally reports memory consumed by the server process or container, which is not the same as memory occupied by live Java objects. The JVM can retain committed heap memory for reuse and also consumes memory outside the heap.
To investigate, open a spark report → Memory tab and check:
- Heap — G1 Old Gen (at last GC): Usually a more reliable indicator of actual RAM usage, as it shows old-generation occupancy after the last relevant garbage collection.
Important: High Old Gen usage in a single snapshot is not proof of a leak. Watch whether post-GC Old Gen occupancy keeps increasing across multiple comparable GC cycles under similar workloads. If it does, investigate the objects being retained with the tools below. A high panel reading alone is not enough to diagnose a leak.
Allocation profiling shows where objects are created, but it doesn't necessarily tell you which objects remain in memory.
For retained-memory investigations, spark provides additional tools.
Heap summary
/spark heapsummary
Provides an overview of objects currently occupying heap memory.
Heap dump
/spark heapdump
Creates a heap dump for more detailed memory analysis.
When to use:
- Investigating steadily increasing heap usage.
- Looking for objects that remain reachable unexpectedly.
- Diagnosing potential memory leaks.
- Understanding what is retaining large amounts of memory.
Warning: Heap dumps can be expensive to generate, potentially causing pauses and requiring substantial disk space. Avoid generating them during peak production load unless necessary.
6. Profiling Duration and Best Practices
I generally recommend 300 seconds (5 minutes) as the default profiling duration.
However, shorter or longer sessions may be more appropriate depending on the problem.
| Duration | Recommended use |
|---|---|
| 60 seconds | Short stress tests or easily reproducible issues |
| 120 seconds | Medium-length stress tests |
| 300 seconds | Standard profiling sessions |
| 600+ seconds | Intermittent issues requiring longer observation |
General recommendations
- Profile while the issue is happening. A report collected under normal conditions may not reveal the actual bottleneck.
- Use representative workloads. Test with realistic player counts, entities, plugins, and server activity.
- Compare different profiling modes. In particular, compare reports with and without
--ignore-sleeping. - Avoid drawing conclusions from a single sample. Profilers are statistical, and short-lived operations may not always appear.
- Measure before and after optimizations. A change that looks beneficial in code may not produce a meaningful performance improvement.
Inclusive vs self time
- Inclusive time: Time spent in a method and its callees.
- Self time: Time spent directly in the method itself.
A method appearing at the top of a flame graph doesn't necessarily mean its own implementation is expensive. The actual bottleneck may be deeper in the call stack.
Allocation rate vs retained memory
- Allocation rate: How much memory is allocated over time.
- Retained memory: How much memory remains reachable and occupied.
High allocation rates can increase garbage collection pressure without necessarily indicating a memory leak.
Combined vs individual threads
Combining profiling results can make it easier to identify expensive code across a group of threads.
However, it can also hide situations where one thread is significantly more overloaded than others.
For Netty profiling, consider collecting both combined and non-combined reports.
7. Quick Command Reference
| Scenario | Command |
|---|---|
| Main-thread overview | /spark profiler start --timeout 300 |
| CPU usage (excluding sleeping) | /spark profiler start --thread * --ignore-sleeping --timeout 300 |
| All-thread profiling (including waiting time) | /spark profiler start --thread * --timeout 300 |
| Memory allocations | /spark profiler start --thread * --alloc --timeout 300 |
| Lag spikes over 200ms | /spark profiler start --only-ticks-over 200 --timeout 300 |
| Lag spikes over 1000ms | /spark profiler start --only-ticks-over 1000 --timeout 300 |
| Netty (separate threads) | /spark profiler start --ignore-sleeping --not-combined --thread Netty.* --regex --timeout 300 |
| Netty (combined threads) | /spark profiler start --ignore-sleeping --thread Netty.* --regex --timeout 300 |
| Heap summary | /spark heapsummary |
| Heap dump | /spark heapdump |
8. Additional Tools for Spark Analysis
Besides spark's built-in viewer, I recommend the following tools for analyzing profiling results.
Disclaimer: I developed both tools, so I'm obviously biased. Suggestions and feedback are welcome. AI-generated interpretations of spark reports, however, are not.
Profiler Utils — Userscript
A userscript that improves the visualization and analysis of spark profiling reports.
It helps make CPU usage, memory allocations, and thread activity easier to inspect and understand.
Spark Profile Tools — Web Analyzer
A fully client-side website for analyzing spark profiling reports.
It provides the same functionality as the userscript, plus additional analysis features:
Plugin resource attribution
Attributes resource usage to individual plugins, making it easier to identify which plugins are responsible for CPU consumption.
This is especially useful when investigating Netty threads, since shared event loops and asynchronous packet processing can make it difficult to identify the responsible plugin directly from a flame graph.
Sleeping time cleanup
Attempts to clean up sleeping/waiting time in all-thread (--thread *) spark reports collected with --ignore-sleeping.
This is not 100% accurate, but provides a useful approximation that can make the results easier to interpret.
Client-side processing
Analysis is performed directly in your browser, without requiring server-side processing.
For more advanced investigations, particularly Netty profiling and per-plugin resource attribution, I recommend using the website.