Skip to content

Commit 2dd9602

Browse files
committed
JBR-9552 Move dynamic call target tables into generated classes to allow garbage collection
1 parent 5bcb770 commit 2dd9602

File tree

5 files changed

+51
-28
lines changed

5 files changed

+51
-28
lines changed

src/java.base/share/classes/com/jetbrains/internal/jbrapi/AccessContext.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,29 @@
4646
*/
4747
class AccessContext {
4848

49+
static final int DYNAMIC_CALL_TARGET_NAME_OFFSET = 128;
50+
@SuppressWarnings("unchecked")
51+
static Supplier<MethodHandle>[] getDynamicCallTargets(Lookup target) {
52+
try {
53+
return (Supplier<MethodHandle>[]) target.findStaticVarHandle(
54+
target.lookupClass(), "dynamicCallTargets", Supplier[].class).get();
55+
} catch (NoSuchFieldException | IllegalAccessException e) {
56+
throw new RuntimeException(e);
57+
}
58+
}
4959
private static final DirectMethodHandleDesc BOOTSTRAP_DYNAMIC_DESC = MethodHandleDesc.ofMethod(
5060
DirectMethodHandleDesc.Kind.STATIC, desc(JBRApiSupport.class), "bootstrapDynamic",
5161
desc(CallSite.class, Lookup.class, String.class, MethodType.class));
5262

5363
private final Map<Class<?>, Boolean> accessibleClasses = new HashMap<>();
5464
final Map<Proxy, Boolean> dependencies = new HashMap<>(); // true for required, false for optional
55-
final List<DynamicCallTarget> dynamicCallTargets = new ArrayList<>();
65+
final List<Supplier<MethodHandle>> dynamicCallTargets = new ArrayList<>();
5666
final Lookup caller;
5767

5868
AccessContext(Lookup caller) {
5969
this.caller = caller;
6070
}
6171

62-
record DynamicCallTarget(String name, MethodTypeDesc descriptor, Supplier<MethodHandle> futureHandle) {}
63-
6472
class Method {
6573
final CodeBuilder writer;
6674
private final boolean methodRequired;
@@ -84,10 +92,9 @@ void invokeDynamic(MethodHandle handle) {
8492
}
8593

8694
void invokeDynamic(MethodType type, Supplier<MethodHandle> futureHandle) {
87-
MethodTypeDesc desc = desc(erase(type));
88-
DynamicCallTarget t = new DynamicCallTarget("dynamic" + dynamicCallTargets.size(), desc, futureHandle);
89-
dynamicCallTargets.add(t);
90-
writer.invokedynamic(DynamicCallSiteDesc.of(BOOTSTRAP_DYNAMIC_DESC, t.name, desc));
95+
String name = String.valueOf((char) (dynamicCallTargets.size() + DYNAMIC_CALL_TARGET_NAME_OFFSET));
96+
dynamicCallTargets.add(futureHandle);
97+
writer.invokedynamic(DynamicCallSiteDesc.of(BOOTSTRAP_DYNAMIC_DESC, name, desc(erase(type))));
9198
}
9299

93100
void invokeDirect(MethodHandleInfo handleInfo) {

src/java.base/share/classes/com/jetbrains/internal/jbrapi/BytecodeUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.lang.invoke.MethodType;
3838
import java.lang.reflect.Method;
3939
import java.util.function.Consumer;
40+
import java.util.function.Supplier;
4041

4142
import static java.lang.classfile.ClassFile.ACC_FINAL;
4243
import static java.lang.classfile.ClassFile.ACC_PUBLIC;
@@ -49,6 +50,8 @@ class BytecodeUtils {
4950
public static final ClassDesc VOID_DESC = desc(void.class);
5051
public static final ClassDesc OBJECT_DESC = desc(Object.class);
5152
public static final ClassDesc OBJECT_ARRAY_DESC = OBJECT_DESC.arrayType();
53+
public static final ClassDesc SUPPLIER_DESC = desc(Supplier.class);
54+
public static final ClassDesc SUPPLIER_ARRAY_DESC = SUPPLIER_DESC.arrayType();
5255
public static final ClassDesc EXTENSION_ARRAY_DESC = desc(long[].class);
5356
public static final ClassDesc PROXY_INTERFACE_DESC = desc(com.jetbrains.exported.JBRApiSupport.Proxy.class);
5457
public static final MethodTypeDesc GET_PROXY_TARGET_DESC = MethodTypeDesc.of(OBJECT_DESC);

src/java.base/share/classes/com/jetbrains/internal/jbrapi/JBRApi.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@
3333
import java.lang.invoke.MethodType;
3434
import java.lang.reflect.Method;
3535
import java.util.Map;
36-
import java.util.concurrent.ConcurrentHashMap;
37-
import java.util.concurrent.ConcurrentMap;
3836
import java.util.function.Function;
39-
import java.util.function.Supplier;
4037

4138
import static java.lang.invoke.MethodHandles.Lookup;
4239

@@ -80,17 +77,6 @@ public class JBRApi {
8077
*/
8178
private static final boolean EXTEND_REGISTRY = Utils.property("jetbrains.runtime.api.extendRegistry", false);
8279

83-
record DynamicCallTargetKey(Class<?> proxy, String name, String descriptor) {}
84-
static final ConcurrentMap<DynamicCallTargetKey, Supplier<MethodHandle>> dynamicCallTargets = new ConcurrentHashMap<>();
85-
86-
public static MethodHandle bindDynamic(Lookup caller, String name, MethodType type) {
87-
if (VERBOSE) {
88-
System.out.println("Binding call site " + caller.lookupClass().getName() + "#" + name + ": " + type);
89-
}
90-
if (!caller.hasFullPrivilegeAccess()) throw new Error("Caller lookup must have full privilege access"); // Authenticity check.
91-
return dynamicCallTargets.get(new DynamicCallTargetKey(caller.lookupClass(), name, type.descriptorString())).get().asType(type);
92-
}
93-
9480
private final ProxyRepository proxyRepository;
9581
private final Boolean[] supportedExtensions;
9682
private final long[] emptyExtensionsBitfield;
@@ -250,4 +236,15 @@ public <T> T getService(Class<T> interFace, Enum<?>... extensions) {
250236
}
251237
return null;
252238
}
239+
240+
public static MethodHandle bindDynamic(Lookup caller, String name, MethodType type) {
241+
int index = name.charAt(0) - AccessContext.DYNAMIC_CALL_TARGET_NAME_OFFSET;
242+
if (VERBOSE) {
243+
System.out.println("Binding call site " + caller.lookupClass().getName() + " #" + index);
244+
}
245+
if (!caller.hasFullPrivilegeAccess()) {
246+
throw new Error("Caller lookup must have full privilege access"); // Authenticity check.
247+
}
248+
return AccessContext.getDynamicCallTargets(caller)[index].get().asType(type);
249+
}
253250
}

src/java.base/share/classes/com/jetbrains/internal/jbrapi/ProxyGenerator.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,10 @@ void init() {
195195
if (JBRApi.VERBOSE) {
196196
System.out.println("Initializing proxy " + interFace.getName());
197197
}
198-
for (var t : accessContext.dynamicCallTargets) {
199-
JBRApi.dynamicCallTargets.put(new JBRApi.DynamicCallTargetKey(
200-
generatedProxy.lookupClass(), t.name(), t.descriptor().descriptorString()
201-
), t.futureHandle());
198+
if (!accessContext.dynamicCallTargets.isEmpty()) {
199+
var table = AccessContext.getDynamicCallTargets(generatedProxy);
200+
for (int i = 0; i < table.length; i++) table[i] = accessContext.dynamicCallTargets.get(i);
201+
202202
}
203203
}
204204

@@ -210,7 +210,7 @@ MethodHandle define(boolean service) {
210210
try {
211211
Object sericeTarget = service && info.targetLookup != null ? createServiceTarget() : null;
212212
generatedProxy = proxyGenLookup.defineHiddenClass(
213-
bytecode, false, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE);
213+
bytecode, false, Lookup.ClassOption.NESTMATE);
214214
MethodHandle constructor = findConstructor();
215215
if (sericeTarget != null) constructor = MethodHandles.insertArguments(constructor, 0, sericeTarget);
216216
return constructor;
@@ -244,10 +244,10 @@ boolean generate() {
244244
cb.withFlags(ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC)
245245
.withSuperclass(superclassDesc)
246246
.withInterfaceSymbols(superinterfaceDescs);
247-
generateFields(cb);
248247
generateConstructor(cb);
249248
generateTargetGetter(cb);
250249
generateMethods(cb);
250+
generateFields(cb);
251251
});
252252
if (JBRApi.VERIFY_BYTECODE) {
253253
List<VerifyError> errors = ClassFile.of().verify(bytecode);
@@ -267,6 +267,14 @@ private void generateFields(ClassBuilder cb) {
267267
if (EXTENSIONS_ENABLED) {
268268
cb.withField("extensions", EXTENSION_ARRAY_DESC, ACC_PRIVATE | ACC_FINAL);
269269
}
270+
if (!accessContext.dynamicCallTargets.isEmpty()) {
271+
cb.withField("dynamicCallTargets", SUPPLIER_ARRAY_DESC, ACC_PRIVATE | ACC_FINAL | ACC_STATIC);
272+
cb.withMethodBody("<clinit>", MethodTypeDesc.of(VOID_DESC), ACC_PRIVATE | ACC_STATIC, m -> m
273+
.loadConstant(accessContext.dynamicCallTargets.size())
274+
.anewarray(SUPPLIER_DESC)
275+
.putstatic(proxyDesc, "dynamicCallTargets", SUPPLIER_ARRAY_DESC)
276+
.return_());
277+
}
270278
}
271279

272280
private void generateConstructor(ClassBuilder cb) {

test/jdk/jb/java/api/backend/RealTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.jetbrains.internal.jbrapi.JBRApi;
3333

3434
import java.io.IOException;
35+
import java.lang.ref.WeakReference;
3536
import java.util.ArrayList;
3637
import java.util.List;
3738
import java.util.Map;
@@ -43,9 +44,16 @@
4344
public class RealTest {
4445

4546
public static void main(String[] args) throws Throwable {
47+
// Plain run.
4648
run();
47-
for (int i = 0; i < 2; i++) {
49+
50+
// Run in an isolated classloader at least 2 times until the proxy gets GC'ed.
51+
WeakReference<?> weak = null;
52+
for (int i = 0; i < 2 || weak.get() != null; i++) {
53+
System.gc();
4854
new IsolatedLoader().loadClass(RealTest.class.getName()).getMethod("run").invoke(null);
55+
if (weak == null) weak = new WeakReference<>(getProxy(Proxy.class));
56+
if (i > 300) throw new Error("Proxy was not collected after 300 iterations");
4957
}
5058
}
5159

0 commit comments

Comments
 (0)