1515 */
1616package com .datadoghq .profiler .memleak ;
1717
18- import com .datadoghq .profiler .AbstractProfilerTest ;
1918import org .junit .jupiter .api .Test ;
20- import org .objectweb .asm .ClassWriter ;
21- import org .objectweb .asm .Label ;
22- import org .objectweb .asm .MethodVisitor ;
23- import org .objectweb .asm .Opcodes ;
2419
2520import java .io .IOException ;
2621import java .lang .reflect .Method ;
2722import java .nio .file .Files ;
2823import java .nio .file .Path ;
29- import java .nio .file .Paths ;
3024
3125/**
3226 * Test to validate that method_map cleanup prevents unbounded memory growth in continuous profiling.
5549 * <li>Combined cleanup: method_map cleanup + class unloading</li>
5650 * </ul>
5751 */
58- public class GetLineNumberTableLeakTest extends AbstractProfilerTest {
52+ public class GetLineNumberTableLeakTest extends AbstractDynamicClassTest {
5953
6054 @ Override
6155 protected String getProfilerCommand () {
6256 // Aggressive sampling to maximize method encounters and GetLineNumberTable calls
6357 return "cpu=1ms,alloc=512k" ;
6458 }
6559
66- private int classCounter = 0 ;
67-
68- /**
69- * Custom ClassLoader for each dynamically generated class.
70- * Using a new ClassLoader for each class ensures truly unique Class objects and jmethodIDs.
71- */
72- private static class DynamicClassLoader extends ClassLoader {
73- public Class <?> defineClass (String name , byte [] bytecode ) {
74- return defineClass (name , bytecode , 0 , bytecode .length );
75- }
76- }
77-
7860 /**
7961 * Generates and loads truly unique classes using ASM bytecode generation.
8062 * Each class has multiple methods with line number tables to trigger GetLineNumberTable calls.
@@ -88,10 +70,10 @@ private Class<?>[] generateUniqueMethodCalls() throws Exception {
8870
8971 for (int i = 0 ; i < 5 ; i ++) {
9072 String className = "com/datadoghq/profiler/generated/TestClass" + (classCounter ++);
91- byte [] bytecode = generateClassBytecode (className );
73+ byte [] bytecode = generateClassBytecode (className , 20 );
9274
9375 // Use a fresh ClassLoader to ensure truly unique Class object and jmethodIDs
94- DynamicClassLoader loader = new DynamicClassLoader ();
76+ IsolatedClassLoader loader = new IsolatedClassLoader ();
9577 Class <?> clazz = loader .defineClass (className .replace ('/' , '.' ), bytecode );
9678 generatedClasses [i ] = clazz ;
9779
@@ -123,66 +105,6 @@ private void invokeClassMethods(Class<?> clazz) {
123105 }
124106 }
125107
126- /**
127- * Generates bytecode for a class with multiple methods.
128- * Each method has line number table entries to trigger GetLineNumberTable allocations.
129- */
130- private byte [] generateClassBytecode (String className ) {
131- ClassWriter cw = new ClassWriter (ClassWriter .COMPUTE_FRAMES | ClassWriter .COMPUTE_MAXS );
132- cw .visit (
133- Opcodes .V1_8 ,
134- Opcodes .ACC_PUBLIC ,
135- className ,
136- null ,
137- "java/lang/Object" ,
138- null );
139-
140- // Generate constructor
141- MethodVisitor constructor =
142- cw .visitMethod (Opcodes .ACC_PUBLIC , "<init>" , "()V" , null , null );
143- constructor .visitCode ();
144- constructor .visitVarInsn (Opcodes .ALOAD , 0 );
145- constructor .visitMethodInsn (
146- Opcodes .INVOKESPECIAL , "java/lang/Object" , "<init>" , "()V" , false );
147- constructor .visitInsn (Opcodes .RETURN );
148- constructor .visitMaxs (0 , 0 );
149- constructor .visitEnd ();
150-
151- // Generate 20 methods per class, each with line number tables
152- for (int i = 0 ; i < 20 ; i ++) {
153- MethodVisitor mv =
154- cw .visitMethod (Opcodes .ACC_PUBLIC , "method" + i , "()I" , null , null );
155- mv .visitCode ();
156-
157- // Add multiple line number entries (this is what causes GetLineNumberTable allocations)
158- Label label1 = new Label ();
159- mv .visitLabel (label1 );
160- mv .visitLineNumber (100 + i , label1 );
161- mv .visitInsn (Opcodes .ICONST_0 );
162- mv .visitVarInsn (Opcodes .ISTORE , 1 );
163-
164- Label label2 = new Label ();
165- mv .visitLabel (label2 );
166- mv .visitLineNumber (101 + i , label2 );
167- mv .visitVarInsn (Opcodes .ILOAD , 1 );
168- mv .visitInsn (Opcodes .ICONST_1 );
169- mv .visitInsn (Opcodes .IADD );
170- mv .visitVarInsn (Opcodes .ISTORE , 1 );
171-
172- Label label3 = new Label ();
173- mv .visitLabel (label3 );
174- mv .visitLineNumber (102 + i , label3 );
175- mv .visitVarInsn (Opcodes .ILOAD , 1 );
176- mv .visitInsn (Opcodes .IRETURN );
177-
178- mv .visitMaxs (0 , 0 );
179- mv .visitEnd ();
180- }
181-
182- cw .visitEnd ();
183- return cw .toByteArray ();
184- }
185-
186108 /**
187109 * Comparison test that validates cleanup effectiveness by running the same workload twice: once
188110 * without cleanup (mcleanup=false) and once with cleanup (mcleanup=true, default). This provides
@@ -316,9 +238,4 @@ public void testCleanupEffectivenessComparison() throws Exception {
316238 }
317239 }
318240
319- private Path tempFile (String name ) throws IOException {
320- Path dir = Paths .get ("/tmp/recordings" );
321- Files .createDirectories (dir );
322- return Files .createTempFile (dir , name + "-" , ".jfr" );
323- }
324241}
0 commit comments