Understanding Java ClassLoader.loadClass() API and Its Performance Impact
This article explains how the java.lang.ClassLoader#loadClass() API works, why frequent concurrent calls can cause thread blocking due to internal synchronization, demonstrates the issue with a sample multithreaded program, analyzes thread‑dump data, and provides practical solutions to mitigate the performance problem.
The java.lang.ClassLoader#loadClass() API is used internally by many third‑party libraries, JDBC drivers, frameworks and application servers to load Java classes into memory; developers rarely call it directly, but when they use Class.forName() or Spring's ClassUtils.forName() , those APIs invoke ClassLoader#loadClass() under the hood.
Because the method synchronizes on a class‑loading lock, frequent calls from multiple threads can degrade performance and even block the application, as demonstrated by a simple program that creates ten threads, each repeatedly loading and instantiating io.ycrash.DummyObject inside an infinite loop.
package io.ycrash.classloader;
public class MyApp extends Thread {
@Override
public void run() {
try {
while (true) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class
myClass = classLoader.loadClass("io.ycrash.DummyObject");
myClass.newInstance();
}
} catch (Exception e) {}
}
public static void main(String args[]) throws Exception {
for (int i = 0; i < 10; ++i) {
new MyApp().start();
}
}
}Running this program and capturing a full 360° data set (thread dumps, GC logs, etc.) with the yCrash tool shows that nine of the ten threads become BLOCKED, each waiting on the same monitor object inside ClassLoader.loadClass() . The tenth thread holds the lock and proceeds, confirming that the synchronized block is the bottleneck.
Inspecting the source of ClassLoader.loadClass() reveals a synchronized (getClassLoadingLock(name)) block, and the getClassLoadingLock() method returns the same lock object for the same class name, causing all threads to contend for the same lock.
protected Class
loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// check if class already loaded, then delegate to parent or bootstrap
...
}
}
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}To resolve the issue, move the class‑loading operation out of the hot loop and cache the Class object once per thread (or globally), then reuse it for instance creation. The revised program initializes the class once in a field and only calls newInstance() inside the loop.
public class MyApp extends Thread {
private Class
myClass = initClass();
private Class
initClass() {
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return classLoader.loadClass("io.ycrash.DummyObject");
} catch (Exception e) {}
return null;
}
@Override
public void run() {
while (true) {
try { myClass.newInstance(); } catch (Exception e) {}
}
}
// main method unchanged
}This change eliminates repeated calls to ClassLoader.loadClass() , preventing the threads from blocking. Additional recommendations include loading classes at application startup, caching frequently used classes, and using troubleshooting tools like fastThread or yCrash to identify problematic frameworks.
Cognitive Technology Team
Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.