Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.IntPredicate;

/**
* Shares class information from multiple classloaders in a single cache.
Expand Down Expand Up @@ -132,6 +133,47 @@ public T find(CharSequence className, int classLoaderKeyId) {
}
}

/**
* Finds information for the given class-name, filtered by class-loader key.
*
* @param className the class-name
* @param classLoaderKeyFilter the filter for the class-loader key
* @return information shared under the class-name and filtered class-loader
* @see ClassLoaderIndex#getClassLoaderKeyId(ClassLoader)
*/
@SuppressWarnings("unchecked")
public T find(CharSequence className, IntPredicate classLoaderKeyFilter) {
Copy link
Collaborator Author

@mcculls mcculls Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note this is structurally very similar to the other find method, but I don't want to deduplicate the code for performance reasons. The other method uses a fast primitive operator inside the loop, whereas this involves a method call.

Copy link

@bric3 bric3 Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth leaving a text comment here, for anyone opening this source file, and not having the PR context :)

final int hash = className.hashCode();
final SharedInfo[] shared = this.shared;
final int slotMask = this.slotMask;

// try to find matching slot, rehashing after each attempt
for (int i = 1, h = hash; true; i++, h = rehash(h)) {
int slot = slotMask & h;
SharedInfo existing = shared[slot];
if (existing != null) {
if (existing.className.contentEquals(className)) {
// apply filter to class-loader key, -1 always matches
if (existing.classLoaderKeyId < 0
|| classLoaderKeyFilter.test(existing.classLoaderKeyId)) {
// use global TICKS as a substitute for access time
// TICKS is only incremented in 'share' for performance reasons
existing.accessed = TICKS.get();
return (T) existing.classInfo;
}
// fall-through and quit; name matched but class-loader didn't
} else if (i < MAX_HASH_ATTEMPTS) {
continue; // rehash and try again
}
}
// quit search when:
// * we find an empty slot (we know there won't be further info)
// * we find a slot with the same name but different class-loader
// * we've exhausted all hash attempts
return null;
}
}

/**
* Shares information for the given class-name, under the given class-loader key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import java.util.HashSet;
import java.util.Set;
import java.util.function.IntPredicate;
import org.junit.jupiter.api.Test;

class ClassInfoCacheTest {
Expand All @@ -19,6 +20,11 @@ void basicOperation() {
ClassLoader myCL = newCL();
ClassLoader notMyCL = newCL();

int myCLKey = ClassLoaderIndex.getClassLoaderKeyId(myCL);

IntPredicate myCLFilter = sameCLKey(myCLKey);
IntPredicate notMyCLFilter = myCLFilter.negate();

assertNull(cache.find("example.test.MyGlobalClass"));
assertNull(cache.find("example.test.MyLocalClass"));
assertNull(cache.find("example.test.NotMyClass"));
Expand All @@ -31,6 +37,14 @@ void basicOperation() {
assertNull(cache.find("example.test.MyLocalClass", notMyCL));
assertNull(cache.find("example.test.NotMyClass", notMyCL));

assertNull(cache.find("example.test.MyGlobalClass", myCLFilter));
assertNull(cache.find("example.test.MyLocalClass", myCLFilter));
assertNull(cache.find("example.test.NotMyClass", myCLFilter));

assertNull(cache.find("example.test.MyGlobalClass", notMyCLFilter));
assertNull(cache.find("example.test.MyLocalClass", notMyCLFilter));
assertNull(cache.find("example.test.NotMyClass", notMyCLFilter));

cache.share("example.test.MyGlobalClass", "my global data");
cache.share("example.test.MyLocalClass", "my local data", myCL);

Expand All @@ -46,6 +60,14 @@ void basicOperation() {
assertNull(cache.find("example.test.MyLocalClass", notMyCL));
assertNull(cache.find("example.test.NotMyClass", notMyCL));

assertEquals("my global data", cache.find("example.test.MyGlobalClass", myCLFilter));
assertEquals("my local data", cache.find("example.test.MyLocalClass", myCLFilter));
assertNull(cache.find("example.test.NotMyClass", myCLFilter));

assertEquals("my global data", cache.find("example.test.MyGlobalClass", notMyCLFilter));
assertNull(cache.find("example.test.MyLocalClass", notMyCLFilter));
assertNull(cache.find("example.test.NotMyClass", notMyCLFilter));

cache.clear();

assertNull(cache.find("example.test.MyGlobalClass"));
Expand All @@ -59,6 +81,14 @@ void basicOperation() {
assertNull(cache.find("example.test.MyGlobalClass", notMyCL));
assertNull(cache.find("example.test.MyLocalClass", notMyCL));
assertNull(cache.find("example.test.NotMyClass", notMyCL));

assertNull(cache.find("example.test.MyGlobalClass", myCLFilter));
assertNull(cache.find("example.test.MyLocalClass", myCLFilter));
assertNull(cache.find("example.test.NotMyClass", myCLFilter));

assertNull(cache.find("example.test.MyGlobalClass", notMyCLFilter));
assertNull(cache.find("example.test.MyLocalClass", notMyCLFilter));
assertNull(cache.find("example.test.NotMyClass", notMyCLFilter));
}

@Test
Expand Down Expand Up @@ -111,12 +141,20 @@ void overflow() {
for (int i = 0; i < 300; i++) {
if (overwritten.contains(i)) {
assertNull(cache.find("example.MyClass" + i), "rem " + i);
assertNull(cache.find("example.MyClass" + i, i), "rem " + i);
assertNull(cache.find("example.MyClass" + i, sameCLKey(i)), "rem " + i);
} else {
assertEquals(i, cache.find("example.MyClass" + i), "add " + i);
assertEquals(i, cache.find("example.MyClass" + i, i), "add " + i);
assertEquals(i, cache.find("example.MyClass" + i, sameCLKey(i)), "add " + i);
}
}
}

private static IntPredicate sameCLKey(int clKey) {
return k -> k == clKey;
}

private static ClassLoader newCL() {
return new ClassLoader() {};
}
Expand Down