question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Memory usage seems surprisingly high, mostly coming from native code (non-Java)

See original GitHub issue

Based on my experience (and some basic testing), JB Compose seems to have a relatively high memory overhead.

Even a basic hello world (auto-generated by IntelliJ) uses 108 MB on my Mac and 146 MB on Linux. Jetbrains Toolbox uses 153 MB on my Mac, so real-world apps have a similar footprint. I use JB Toolbox every day and it pretty consistently has this much overhead. This is significantly higher memory usage than Flutter, and only a little bit better than Electron.

What are your thoughts on this? From what I can tell, most of this memory usage is mostly coming from native code outside the Java heap (see below).

Here are some more details on my (unscientific) benchmarking:

Basic Benchmarking

Here are the results of my (admittedly bad) memory usage benchmarks:

Application Framework Memory Usage (Mac) Memory Usage (Linux)
JB Toolbox JB Compose 153 MB 253 MB
Compose Hello World JB Compose 108 MB 146 MB
Flutter hello world Flutter 59.9 MB 113 MB
Spotify Electron 313 MB ~300 MB
JavaFX Hello world JavaFX (can’t compile) 209 MB
Discord Electron ~600 MB ~300 MB
MultiMC QT 58.3 MB TODO
Hexchat GTK3 N/A 45 MB

Strangely the Flutter on Linux has much higher memory usage than the one on Mac 🤷

My Mac system has 32 GB of RAM, my Linux system only has 8 GB.

The memory usage spikes significantly after startup (both for Toolbox & Hello World). Sometimes, the memory usage goes down when I explicitly trigger a garbage collection. The results I gave in my benchmark table are the “average” memory usage. I have occasionally seen significantly less memory, and it is much worse at startup.

Profiling & Thoughts

Would it be possible to tune the memory usage to be more lightweight? Is that even a reasonable goal?

It appears that the vast of the memory seems to be used by native code outside of the heap. I can fix the java heap size to 30MB with Xms30M -Xmx30M and the “hello world” app still uses 109MB. Unfortunately, the memory analysis of Yourkit and VisualVM (my heap profiling tools) doesn’t seem able to analyze this native memory. I assume a significant portion of this is what’s used by the Skia bindings. I’m not really a graphics expert, so I don’t really know how to analyze this any further 😦

I don’t know much about graphics frameworks, but I would expect the memory usage of Flutter and JB Compose to be similar (Electron is a different story). Flutter and Compose both use garbage collected VMs and rely on Skia for rendering. So it’s somewhat surprising that the memory usage of JB Compose is so much higher than Flutter (at least on my Mac).

I’m sorry for the long post (and my imprecise benchmarks), I’m just curious about the (seemingly) high memory usage in a framework which otherwise seems really amazing 😄 It appears to be coming from native graphics code, which I don’t really know much about…

-- Techcable

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:18
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

5reactions
Techcablecommented, Dec 31, 2021

It’s extremely difficult to run a compose app with Grall native-image, because of the significant restrictions on reflection. I’m currently running into problems with Kotlin coroutines and gave up 😦

Also using the “fallback” image doesn’t really do much for memory usage. In my tests, it actually made it significantly worse.

In my tests, it appears that most of the memory is not used by the java heap (only 8MB), and also very little is used by class metadata (15MB). Without all the other overhead, the ideal app footprint would be 24-32MB (combining heap and metaspace).

Presumably, Graal’s memory savings come mostly from reducing class footprint, not externally allocated native memory. As a result, I would recommend focusing on optimization and profiling with the standard JVM instead.

You can print a detailed summary of JVM heap usage with -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics -XX:NativeMemoryTracking=summary.

I have done a detailed analysis of my hello world app and have some recommendations below 😄

Profiling Results

Total memory usage is about 103.63 MiB according to the JVM. (Although see below note, the system claims “150MB”)

Here’s a breakdown of the different memory usage (based on -XX:+PrintNMTStatistics):

It appears to me that the biggest bloat comes from thread stack size. It uses more than heap and codespace combined.

System “Committed” Memory “Reserved” memory Comments
Total 103.63MB 1.11 GB “committed” is what’s actually used
Java heap 8.00 MB 16.00 MiB Explicitly limited with -Xmx16M -Xms8M
“Classes” 2.33MB 1GB 4138 loaded classes
Metaspace 15.36 MiB 15.36 MiB Includes class metadata
Thread 50.29 MB 50.29 MiB Appears to come almost entirely from stack size
Code (JIT) 3.94 MiB 5.26 MiB Explicitly limited with flag (JIT doesn’t seem to need much space)
“Shared class space” 11.83Mib 12MiB Shared between all JVMs (see below)
“Symbol” 5.01 MiB 5.01 MiB Not sure what this is (constant pool?).

Some entries have been omitted in the interest of space (see below for full data).

Cass metaspace (15MB) is something that cannot really be avoided,

If we add together class metaspace and heap we only get 23MB of usage (that’s only 1/4 of the total).

As I mentioned above, seems the main culprit of this memory bloat is threads. It uses more memory than the heap and codespace combined. Of the 103 MB of total memory, thread stack sizes use up almost half!

In our case the process spawned 21 different threads, using a total of 52.3MB of stack space.

I would recommend figuring out ways to reduce the number of background threads and also reducing the size of their stacks.

The JVM has a flag -Xss<size> to change the default stack size. By simply setting -Xss500K I was able to cut thread stack usage in half, saving 25M from the total memory amount used 🚀

Based on this encouraging result, it would appear that memory optimization should start by focusing on threads.

In addition to the global flag, Java threads can have their stack size explicitly set although the result is platform dependent.

Presumably, there are options to reduce the number of background threads spawned by the JVM and third-party libraries. In general, The JVM tends to be optimized for large heaps where stack size and number of threads are insignificant. In our case it’s the biggest usage of memory!

It looks like JB Toolbox is using 52 threads at the moment. Extrapolating from the stack usage we have here, that would put stack usage for that app at almost 100 MB. In some cases it can be lower like 42 or 35 (but that’s still a ton of memory).

class data sharing

Class data sharing allows part of the class metadata to be shared between all JVM processes on the system. In our case it looks like 11.8MB is shared. I am not sure if this is counted as part of “class metadata” or if it’s separate. See oracle docs), so there is 11.8MB of memory that is shared between all the JVM processes on my system 🎊

I believe it is possible to customize this, so this is also another possible avenue for optimization.

Extra Notes & Raw Output

Original command line and raw output The original command line is `java -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics -XX:NativeMemoryTracking=summary -Xshare:on -XX:ReservedCodeCacheSize=5M -XX:+UseS erialGC -Xmx16M -Xms8M -jar build/compose/jars/compose-hello-world-macos-arm64-1.0.0.jar > summary.txt`
A note on JVM flags used I've tried tuning flags to optimize memory usage of the hello-world. I was able to limit the heap to 16Mb (`-Xmx16M`). Adding `XX:+UseSerialGC` seemed to shave of about 5MBs (instead of UseG1GC). Most other flags I tried beyond that didn't make much of a difference.

Limitting the code space saved maybe 1MB at most.

Asside from limiting heap and setting GC, tuning flags seems like a dead end.

Different methods give different results for "overall memory usage". There are a couple different ways to track overall memory usage (on my mac). Some of them give different results - The JVM's total "committed" memory: 103.63 MiB - bpytop claims ~124MB (I think this gives RRS on linux) - Activity monitor claims ~150MB - Real memory 124MB - Shared memory 134MB (presumably for [class data sharing](https://docs.oracle.com/en/java/javase/13/vm/class-data-sharing.html#GUID-7EAA3411-8CF0-4D19-BD05-DF5E1780AA91)] - Private memory size 26MB
3reactions
ScottPiercecommented, Dec 27, 2021

You should check memory usage when Compose JVM Desktop is compiled to a GraalVM Native Image.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Memory consumption issues of a Java program - Stack Overflow
Leaks in native code does seem like a possibility. We don't use any native libraries that I know of though, not even JDBC....
Read more >
Encountering an unaccounted High Memory Usage by Java?
Java heap is 65% of RAM, which is 13gb (Xmx=13g); After running for a while, memory fills up and java RSS usage shows...
Read more >
Choosing Java instead of C++ for low-latency systems
Well, C++ might be “low latency” when it comes to executing code, ... For developers who haven't use both languages, here's why: Java...
Read more >
High Memory utilization and their root causes | Dynatrace
Increasing memory is the obvious workaround for memory leaks or badly written software. Let's discuss the two most common causes for Java high...
Read more >
Java Performance Tuning Flashcards | Quizlet
interpreter may slow down if it has to interprete every instruction, but JIT can generate native code and cache them for frequently used...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found