Skip to content

Fix classloading in ConstructorCache #6233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/bugfix-AWSSDKforJavav2-641f6c9.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "bugfix",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Fix an issue where the CRC64NVME class cannot be loaded from CRT in some situations, even if `aws-crt` is correctly included in the application classpath."
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,25 @@

import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.utils.ClassLoaderHelper;
import software.amazon.awssdk.utils.Logger;

/**
* A cache that stores classes and their constructors by class name and class loader.
* A cache that stores classes and exposes a method to retrieve its zero-argument constructor.
* <p>
* This cache uses weak references to both class loaders and classes, allowing them to be garbage collected
* when no longer needed. It provides methods to retrieve the zero-argument constructor for a class,
* based on the current thread's context class loader or the system class loader.
* This cache stores weak references to loaded classes, allowing them to be garbage collected at any point.
* <p>
* Classes are loaded by first attempting to load it via the thread context {@code ClassLoader} (or system {@code ClassLoader} if
* the calling thread does not have one). If that fails, it will attempt using the {@code ClassLoader} that loaded
* {@link ClassLoaderHelper}.
* <p>
* If a class or its zero-argument constructor cannot be found, an empty result is returned.
*
* @see ClassLoaderHelper#loadClass(String, boolean, Class[])
*/
@SdkInternalApi
public final class ConstructorCache {
Expand All @@ -43,36 +45,36 @@ public final class ConstructorCache {
* Cache storing classes by class name and class loader.
* Uses weak references to allow garbage collection when not needed.
*/
private final Map<String, Map<ClassLoader, Optional<WeakReference<Class<?>>>>> classesByClassName =
private final Map<String, Optional<WeakReference<Class<?>>>> classesByClassName =
new ConcurrentHashMap<>();

/**
* Retrieve the class for the given class name from the context or system class loader.
* Returns an empty result if the class is not found.
*/
private Optional<Class<?>> getClass(String className) {
Map<ClassLoader, Optional<WeakReference<Class<?>>>> classesByClassLoader =
classesByClassName.computeIfAbsent(className, k -> Collections.synchronizedMap(new WeakHashMap<>()));

ClassLoader classLoader = ClassLoaderHelper.contextClassLoader();
Optional<WeakReference<Class<?>>> classRef = classesByClassLoader.computeIfAbsent(classLoader, k -> {
Optional<WeakReference<Class<?>>> classRef = classesByClassName.computeIfAbsent(className, k -> {
try {
Class<?> clazz = classLoader.loadClass(className);
Class<?> clazz = ClassLoaderHelper.loadClass(k, false);
return Optional.of(new WeakReference<>(clazz));
} catch (ClassNotFoundException e) {
return Optional.empty();
}
});

// if the WeakReference to the class has been garbage collected, remove it from the cache and try again
// Were we able to find this class?
if (classRef.isPresent()) {
Class<?> clazz = classRef.get().get();
// Class hasn't been GC'd
if (clazz != null) {
return Optional.of(clazz);
}
classesByClassLoader.remove(classLoader);
// if the WeakReference to the class has been garbage collected, it has been unloaded.
// Remove it from the cache and try a fresh load
classesByClassName.remove(className);
return getClass(className);
}

return Optional.empty();
}

Expand Down
Loading