A healthy diet
for your Java
application
Marharyta Nedzelska, @jMargaritaN
17.04.2024
Marharyta Nedzelska
Software Engineer @ Sonar
Conferences speaker & organizer
Java/Kotlin/Scala/...
Twitter: @jMargaritaN
https://www.freepik.com/vectors/ukraine-war
Ukraine war vector created by starline - www.freepik.com
Disclaimer
Disclaimer
How it all started?
Aleksey Shipilëv
Principal Engineer, Languages
and Runtimes, Amazon Web
Services
OpenJDK
JMH, JCStress, JOL
https://sonarcloud.io/project/overview?id=shipilev_jdk
I want to analyze Java & C++
code of OpenJDK in SonarCloud
…
using 3 Gb of RAM.
But I can’t :(
Aleksey Shipilëv
CI …
IDE
At the same time…
Am I making my
application “fit” too?
Is there any problem?
How to make your app “fit”?
Garbage Collector is not a wizard
I can’t collect objects that are
still referenced!
THE SYMPTOMS
Body
● Clothes too small
● Fat % too high
● Difficult to
run/walk/breathe
● BMI too high
● Always hungry
● Not satisfied with the
appearance…
● OutOfMemoryError
● More memory doesn’t help
● Much time on GC (GC
pauses)
● Spending too much CPU
● …
Application
Is there any problem?
How to make your app “fit”?
Let’s measure
Body
● Weight
● Fat & muscles
● BMI
● Heart rate
● Blood tests
● Eating habits
● …
● Memory usage
● Time
● CPU usage
● Heapdump
● Logs
● …
Application
Is there a leak?
Is there a leak?
Here are the heapdump, logs
and some memory analysis to
help you investigate…
Aleksey Shipilëv
java.lang.OutOfMemoryError: GC overhead limit exceeded
at com.google.protobuf.Utf8$UnsafeProcessor.decodeUtf8(na:2669)
at com.google.protobuf.Utf8.decodeUtf8(na:2905)
…
at com.sonar.A.D.A(na:648)
at com.sonar.A.D.B(na:2257)
at com.sonar.A.D.A(na:3069)
at com.sonar.cpp.F.readUcfg(na:2348)
java.lang.OutOfMemoryError: GC overhead limit exceeded
at com.google.protobuf.Utf8$UnsafeProcessor.decodeUtf8(na:2669)
at com.google.protobuf.Utf8.decodeUtf8(na:2905)
…
at com.sonar.A.D.A(na:648)
at com.sonar.A.D.B(na:2257)
at com.sonar.A.D.A(na:3069)
at com.sonar.cpp.F.readUcfg(na:2348)
java.lang.OutOfMemoryError: GC overhead limit exceeded
at com.google.protobuf.Utf8$UnsafeProcessor.decodeUtf8(na:2669)
at com.google.protobuf.Utf8.decodeUtf8(na:2905)
…
at com.sonar.A.D.A(na:648)
at com.sonar.A.D.B(na:2257)
at com.sonar.A.D.A(na:3069)
at com.sonar.cpp.F.readUcfg(na:2348)
Stacktrace is useless!!!
Is there any problem?
How to make your app “fit”?
Let’s measure
Let’s analyze
When static fields are eligible for garbage collection?
A) When the last instance is collected
B) When Class<.> instance is collected
C) When JVM exits
D) When class is unloaded
E) None of the above
32
When static fields are eligible for garbage collection?
A) When the last instance is collected
B) When Class<.> instance is collected
C) When JVM exits
D) When class is unloaded
E) None of the above
33
Instance Class Classloader
Static field
Instance Class Classloader
Static field
Instance Class Classloader
Static field
Instance Class Classloader
Static field
When static fields are eligible for garbage collection?
A) When the last instance is collected
B) When Class<.> instance is collected
C) When JVM exits
D) When class is unloaded
E) None of the above
F) When classloader is collected
38
Static is like sugar
● Static members live almost “forever”
● Static members should be avoided
● Static members shouldn’t be mutable
● Static members shouldn’t be updated from non-static methods
S2386
S2696
Is there any problem?
How to make your app “fit”?
Let’s measure
Let’s analyze
Find the root cause
equals() / hashCode() - carbs
● Should be used properly
● Always override equals() and hashCode() together
● Contract: if equals() returns true, hashCode() must be the same
● HashMap keys must be immutable
S1206
● https://jqno.nl/equalsverifier/
● https://github.com/jqno/equalsverifier
@Test
void test() {
EqualsVerifier
.forClass(FullName.class)
.verify();
}
Is there any problem?
How to make your app “fit”?
Let’s measure
Let’s analyze
Find the root cause
Fix it
Inner classes
● Should be static
● If still need it, take care of the lifecycle and outer class reference
S2694
ThreadLocals
● ThreadLocal lives as long as thread is alive
● Should be unset manually
S5164
Is there any problem?
How to make your app “fit”?
Let’s measure
Let’s analyze
Find the root cause
Fix it
Improve
Memory leaks Vs Memory footprint
Memory leak
A memory leak is an
unintentional form of
memory consumption
whereby the developer fails
to free an allocated block of
memory when no longer
needed.
- OWASP
Memory footprint
Memory footprint refers to
the amount of main
memory that a program
uses or references while
running.
- Wikipedia
Fixing memory leaks Reducing memory footprint
Reducing memory footprint
● Remove references manually
● Custom classloaders
try(var loader = new CustomClassloader(Example1.class.getClassLoader())) {
var clazz = loader.loadClass("com.example.classloader.MyExampleClass");
var instance = clazz.getConstructor().newInstance();
clazz.getMethod("doSomething").invoke(instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
Reducing memory footprint
● Remove references manually
● Custom classloaders
● Minimize the living scope
Reducing memory footprint
● Remove references manually
● Custom classloaders
● Minimize the living scope
● Do all the necessary cleanup
Reducing memory footprint
● Remove references manually
● Custom classloaders
● Minimize the living scope
● Do all the necessary cleanup
● Use WeakReference
private WeakReference<BigObject> bigObject;
public void execute() {
var ref = bigObject.get();
if (ref == null) {
ref = recreateBigObject();
bigObject = new WeakReference<>(ref);
}
ref.hello();
}
Ballance is the key
● Losing too much
● Performance costs
● Need to find a balance
Is there any problem?
How to make your app “fit”?
Let’s measure
Let’s analyze
Find the root cause
Fix it
Improve
Fix it
Prevent
Remember how it all started?
I want to analyze Java & C++
code of OpenJDK in SonarCloud
…
using 3 Gb of RAM.
But I can’t :(
Aleksey Shipilëv
void run() {
var plugins = List.of(new JavaPlugin(), new CPPPlugin());
plugins.forEach(Plugin::execute);
}
MEMORY
What can we do?
● Make Java plugin last?
● Add some cleanup between plugins
● Reduce the life of Plugin
● Maybe Queue is better than List here
void run() {
ArrayDeque<Plugin> plugins = ...
for (var plugin = plugins.poll(); plugin != null;
plugin = plugins.poll()) {
plugin.execute();
}
}
● Follow a “healthy diet”
● Monitor memory usage
● GC logs
● Perform stress testing
● Use VisualVm, memory
analyzer,...
● Add tests
Java Object Layout (JOL)
https://github.com/openjdk/jol
@Test
public void myTest() {
var big = new BigObject().new SmallObject();
Assert.assertTrue(
GraphLayout.parseInstance(big).totalCount() < MAX_SIZE);
}
A healthy diet for your Java application
● Avoid statics and mutability
● Clean ThreadLocals
● Free resources
● Avoid Non-static inner classes
● If still need them, be careful
● Reduce the scope of references
● Monitor memory usage
How the story ended?
Useful links
● https://rules.sonarsource.com/
● https://owasp.org/www-community/vulnerabilities/Memory_leak
● https://www.baeldung.com/java-memory-leaks
● https://www.baeldung.com/java-static-fields-gc
● https://www.baeldung.com/java-weak-reference
Useful tools
● Visual VM : https://visualvm.github.io/
● Eclipse MAT : https://www.eclipse.org/mat/
● EqualsVerifier : https://jqno.nl/equalsverifier/
● Java Flight Recorder : https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH173
● JOL : https://github.com/openjdk/jol
● Sonar : https://www.sonarsource.com/products/sonarqube/
Some kudos
Evgeny Mandrikov
Some kudos
JVM squad
Thanks for your
attention!
Questions?

A healthy diet for your Java application Devoxx France.pdf