From ca40b15acbd0666b65cbd538d7d2323ee4868567 Mon Sep 17 00:00:00 2001 From: GongDewei Date: Wed, 3 Sep 2025 23:38:08 +0800 Subject: [PATCH 1/4] Fix ClassLoader cache OOM issue with WeakHashMap Replace hashCode-based cache with WeakHashMap to automatically cleanup dynamic class caches when ClassLoader is garbage collected. - Change CLASS_LOADER_TYPE_CACHE from Map to Map - Use Collections.synchronizedMap(new WeakHashMap<>()) for automatic cleanup - Add separate BOOTSTRAP_TYPE_CACHE for null ClassLoader handling - Add comprehensive unit tests validating cache behavior Fixes OOM issues in rule engine scenarios like Drools where dynamic classes are frequently created and ClassLoaders are recycled. --- .../agent/builder/SWDescriptionStrategy.java | 26 +- .../SWDescriptionStrategyCacheTest.java | 239 ++++++++++++++++++ 2 files changed, 259 insertions(+), 6 deletions(-) create mode 100644 apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java diff --git a/apm-sniffer/bytebuddy-patch/src/main/java/net/bytebuddy/agent/builder/SWDescriptionStrategy.java b/apm-sniffer/bytebuddy-patch/src/main/java/net/bytebuddy/agent/builder/SWDescriptionStrategy.java index 62e8a0606c..0d10a039bb 100644 --- a/apm-sniffer/bytebuddy-patch/src/main/java/net/bytebuddy/agent/builder/SWDescriptionStrategy.java +++ b/apm-sniffer/bytebuddy-patch/src/main/java/net/bytebuddy/agent/builder/SWDescriptionStrategy.java @@ -34,10 +34,12 @@ import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -125,9 +127,16 @@ static class SWTypeDescriptionWrapper extends TypeDescription.AbstractBase imple /** * Original type cache. - * classloader hashcode -> ( typeName -> type cache ) + * ClassLoader -> (typeName -> TypeCache) + * Using WeakHashMap to automatically clean up cache when ClassLoader is garbage collected. */ - private static final Map> CLASS_LOADER_TYPE_CACHE = new ConcurrentHashMap<>(); + private static final Map> CLASS_LOADER_TYPE_CACHE = + Collections.synchronizedMap(new WeakHashMap<>()); + + /** + * Bootstrap ClassLoader cache (null key cannot be stored in WeakHashMap) + */ + private static final Map BOOTSTRAP_TYPE_CACHE = new ConcurrentHashMap<>(); private static final List IGNORED_INTERFACES = Arrays.asList(EnhancedInstance.class.getName()); @@ -153,10 +162,15 @@ public SWTypeDescriptionWrapper(TypeDescription delegate, String nameTrait, Clas } private TypeCache getTypeCache() { - int classLoaderHashCode = classLoader != null ? classLoader.hashCode() : 0; - Map typeCacheMap = CLASS_LOADER_TYPE_CACHE.computeIfAbsent(classLoaderHashCode, k -> new ConcurrentHashMap<>()); - TypeCache typeCache = typeCacheMap.computeIfAbsent(typeName, k -> new TypeCache(typeName)); - return typeCache; + if (classLoader == null) { + // Bootstrap ClassLoader - use separate cache since null key cannot be stored in WeakHashMap + return BOOTSTRAP_TYPE_CACHE.computeIfAbsent(typeName, k -> new TypeCache(typeName)); + } else { + // Regular ClassLoader - use WeakHashMap for automatic cleanup + Map typeCacheMap = CLASS_LOADER_TYPE_CACHE.computeIfAbsent( + classLoader, k -> new ConcurrentHashMap<>()); + return typeCacheMap.computeIfAbsent(typeName, k -> new TypeCache(typeName)); + } } @Override diff --git a/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java b/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java new file mode 100644 index 0000000000..dee14c77c6 --- /dev/null +++ b/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.bytebuddy.agent.builder; + +import org.junit.Test; +import org.junit.Assert; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.net.URL; +import java.util.Map; + +/** + * 测试SWDescriptionStrategy中WeakHashMap缓存机制的行为 + */ +public class SWDescriptionStrategyCacheTest { + + @Test + public void testWeakHashMapCacheCleanup() throws Exception { + // 获取静态缓存字段 + Field cacheField = SWDescriptionStrategy.SWTypeDescriptionWrapper.class + .getDeclaredField("CLASS_LOADER_TYPE_CACHE"); + cacheField.setAccessible(true); + @SuppressWarnings("unchecked") + Map> cache = + (Map>) cacheField.get(null); + + // 记录初始缓存大小 + int initialCacheSize = cache.size(); + + // 创建测试用的ClassLoader + URLClassLoader testClassLoader = new URLClassLoader(new URL[0], null); + String testTypeName = "com.test.DynamicClass"; + + // 创建SWTypeDescriptionWrapper实例 + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", testClassLoader, testTypeName); + + // 通过反射调用getTypeCache方法触发缓存 + Method getTypeCacheMethod = wrapper.getClass() + .getDeclaredMethod("getTypeCache"); + getTypeCacheMethod.setAccessible(true); + SWDescriptionStrategy.TypeCache typeCache = + (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper); + + // 验证缓存中存在该ClassLoader + Assert.assertTrue("Cache should contain the test ClassLoader", + cache.containsKey(testClassLoader)); + Assert.assertNotNull("TypeCache should be created", typeCache); + Assert.assertEquals("Cache size should increase by 1", + initialCacheSize + 1, cache.size()); + + // 清除ClassLoader引用,准备GC测试 + testClassLoader = null; + wrapper = null; + typeCache = null; + + // 强制垃圾回收 + System.gc(); + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + // WeakHashMap应该自动清理被回收的ClassLoader条目 + int attempts = 0; + int currentCacheSize = cache.size(); + while (currentCacheSize > initialCacheSize && attempts < 20) { + System.gc(); + Thread.sleep(50); + currentCacheSize = cache.size(); + attempts++; + } + + System.out.println("Cache size after GC: " + currentCacheSize + + " (initial: " + initialCacheSize + ", attempts: " + attempts + ")"); + + // 验证WeakHashMap的清理机制工作正常 + Assert.assertTrue("WeakHashMap should clean up entries or attempts should be reasonable", + currentCacheSize <= initialCacheSize + 1 && attempts < 20); + } + + @Test + public void testBootstrapClassLoaderHandling() throws Exception { + // 获取Bootstrap ClassLoader缓存字段 + Field bootstrapCacheField = SWDescriptionStrategy.SWTypeDescriptionWrapper.class + .getDeclaredField("BOOTSTRAP_TYPE_CACHE"); + bootstrapCacheField.setAccessible(true); + @SuppressWarnings("unchecked") + Map bootstrapCache = + (Map) bootstrapCacheField.get(null); + + int initialBootstrapCacheSize = bootstrapCache.size(); + + // 测试Bootstrap ClassLoader (null) 的处理 + String testTypeName = "test.BootstrapClass"; + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", null, testTypeName); + + // 通过反射调用getTypeCache方法 + Method getTypeCacheMethod = wrapper.getClass() + .getDeclaredMethod("getTypeCache"); + getTypeCacheMethod.setAccessible(true); + SWDescriptionStrategy.TypeCache typeCache = + (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper); + + // 验证Bootstrap ClassLoader的缓存处理 + Assert.assertNotNull("TypeCache should be created for bootstrap classloader", typeCache); + Assert.assertTrue("Bootstrap cache should contain the type", + bootstrapCache.containsKey(testTypeName)); + Assert.assertEquals("Bootstrap cache size should increase by 1", + initialBootstrapCacheSize + 1, bootstrapCache.size()); + } + + @Test + public void testMultipleClassLoadersIndependentCache() throws Exception { + Field cacheField = SWDescriptionStrategy.SWTypeDescriptionWrapper.class + .getDeclaredField("CLASS_LOADER_TYPE_CACHE"); + cacheField.setAccessible(true); + @SuppressWarnings("unchecked") + Map> cache = + (Map>) cacheField.get(null); + + int initialCacheSize = cache.size(); + + // 创建两个不同的ClassLoader + URLClassLoader classLoader1 = new URLClassLoader(new URL[0], null); + URLClassLoader classLoader2 = new URLClassLoader(new URL[0], null); + String testTypeName = "com.test.SameClassName"; + + // 为两个ClassLoader创建相同类名的TypeCache + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper1 = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", classLoader1, testTypeName); + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper2 = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", classLoader2, testTypeName); + + // 通过反射调用getTypeCache方法 + Method getTypeCacheMethod = + SWDescriptionStrategy.SWTypeDescriptionWrapper.class.getDeclaredMethod("getTypeCache"); + getTypeCacheMethod.setAccessible(true); + + SWDescriptionStrategy.TypeCache typeCache1 = + (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper1); + SWDescriptionStrategy.TypeCache typeCache2 = + (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper2); + + // 验证两个ClassLoader有独立的缓存项 + Assert.assertNotNull("TypeCache1 should be created", typeCache1); + Assert.assertNotNull("TypeCache2 should be created", typeCache2); + Assert.assertNotSame("TypeCaches should be different instances", typeCache1, typeCache2); + + // 验证缓存结构 + Assert.assertEquals("Cache should contain both classloaders", + initialCacheSize + 2, cache.size()); + Assert.assertTrue("Cache should contain classloader1", cache.containsKey(classLoader1)); + Assert.assertTrue("Cache should contain classloader2", cache.containsKey(classLoader2)); + + // 验证每个ClassLoader有独立的类型缓存 + Map typeCacheMap1 = cache.get(classLoader1); + Map typeCacheMap2 = cache.get(classLoader2); + + Assert.assertNotNull("ClassLoader1 should have type cache map", typeCacheMap1); + Assert.assertNotNull("ClassLoader2 should have type cache map", typeCacheMap2); + Assert.assertNotSame("Type cache maps should be different", typeCacheMap1, typeCacheMap2); + + Assert.assertTrue("ClassLoader1 cache should contain the type", + typeCacheMap1.containsKey(testTypeName)); + Assert.assertTrue("ClassLoader2 cache should contain the type", + typeCacheMap2.containsKey(testTypeName)); + } + + @Test + public void testConcurrentAccess() throws Exception { + // 测试并发访问场景 + final String testTypeName = "com.test.ConcurrentClass"; + final int threadCount = 10; + final Thread[] threads = new Thread[threadCount]; + final URLClassLoader[] classLoaders = new URLClassLoader[threadCount]; + final SWDescriptionStrategy.TypeCache[] results = new SWDescriptionStrategy.TypeCache[threadCount]; + + // 创建多个线程同时访问缓存 + for (int i = 0; i < threadCount; i++) { + final int index = i; + classLoaders[i] = new URLClassLoader(new URL[0], null); + threads[i] = new Thread(() -> { + try { + SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper = + new SWDescriptionStrategy.SWTypeDescriptionWrapper( + null, "test", classLoaders[index], testTypeName); + + Method getTypeCacheMethod = wrapper.getClass() + .getDeclaredMethod("getTypeCache"); + getTypeCacheMethod.setAccessible(true); + results[index] = (SWDescriptionStrategy.TypeCache) + getTypeCacheMethod.invoke(wrapper); + } catch (Exception e) { + Assert.fail("Concurrent access should not throw exception: " + e.getMessage()); + } + }); + } + + // 启动所有线程 + for (Thread thread : threads) { + thread.start(); + } + + // 等待所有线程完成 + for (Thread thread : threads) { + thread.join(1000); // 最多等待1秒 + } + + // 验证所有结果 + for (int i = 0; i < threadCount; i++) { + Assert.assertNotNull("Result " + i + " should not be null", results[i]); + } + + System.out.println("Concurrent access test completed successfully with " + threadCount + " threads"); + } +} \ No newline at end of file From 23345ac9c95f16007127852c38528009b6bf2984 Mon Sep 17 00:00:00 2001 From: Gong Dewei Date: Thu, 4 Sep 2025 00:25:50 +0800 Subject: [PATCH 2/4] Update apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java b/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java index dee14c77c6..e1fa9b5ae0 100644 --- a/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java +++ b/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java @@ -28,7 +28,7 @@ import java.util.Map; /** - * 测试SWDescriptionStrategy中WeakHashMap缓存机制的行为 + * Tests the behavior of the WeakHashMap caching mechanism in SWDescriptionStrategy. */ public class SWDescriptionStrategyCacheTest { From 6c1c6196e3cb92e405d9115adcc4a25024f5bd8b Mon Sep 17 00:00:00 2001 From: GongDewei Date: Thu, 4 Sep 2025 09:06:56 +0800 Subject: [PATCH 3/4] Translate Chinese comments to English in SWDescriptionStrategyCacheTest - Convert all Chinese comments to English for better international collaboration - Maintain original functionality and test logic - Improve code readability for global contributors --- .../SWDescriptionStrategyCacheTest.java | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java b/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java index e1fa9b5ae0..90f9e4a437 100644 --- a/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java +++ b/apm-sniffer/bytebuddy-patch/src/test/java/net/bytebuddy/agent/builder/SWDescriptionStrategyCacheTest.java @@ -34,7 +34,7 @@ public class SWDescriptionStrategyCacheTest { @Test public void testWeakHashMapCacheCleanup() throws Exception { - // 获取静态缓存字段 + // Get static cache field Field cacheField = SWDescriptionStrategy.SWTypeDescriptionWrapper.class .getDeclaredField("CLASS_LOADER_TYPE_CACHE"); cacheField.setAccessible(true); @@ -42,44 +42,44 @@ public void testWeakHashMapCacheCleanup() throws Exception { Map> cache = (Map>) cacheField.get(null); - // 记录初始缓存大小 + // Record initial cache size int initialCacheSize = cache.size(); - // 创建测试用的ClassLoader + // Create test ClassLoader URLClassLoader testClassLoader = new URLClassLoader(new URL[0], null); String testTypeName = "com.test.DynamicClass"; - // 创建SWTypeDescriptionWrapper实例 + // Create SWTypeDescriptionWrapper instance SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper = new SWDescriptionStrategy.SWTypeDescriptionWrapper( null, "test", testClassLoader, testTypeName); - // 通过反射调用getTypeCache方法触发缓存 + // Call getTypeCache method via reflection to trigger caching Method getTypeCacheMethod = wrapper.getClass() .getDeclaredMethod("getTypeCache"); getTypeCacheMethod.setAccessible(true); SWDescriptionStrategy.TypeCache typeCache = (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper); - // 验证缓存中存在该ClassLoader + // Verify that the ClassLoader exists in cache Assert.assertTrue("Cache should contain the test ClassLoader", cache.containsKey(testClassLoader)); Assert.assertNotNull("TypeCache should be created", typeCache); Assert.assertEquals("Cache size should increase by 1", initialCacheSize + 1, cache.size()); - // 清除ClassLoader引用,准备GC测试 + // Clear ClassLoader references, prepare for GC test testClassLoader = null; wrapper = null; typeCache = null; - // 强制垃圾回收 + // Force garbage collection System.gc(); Thread.sleep(100); System.gc(); Thread.sleep(100); - // WeakHashMap应该自动清理被回收的ClassLoader条目 + // WeakHashMap should automatically clean up garbage collected ClassLoader entries int attempts = 0; int currentCacheSize = cache.size(); while (currentCacheSize > initialCacheSize && attempts < 20) { @@ -92,14 +92,14 @@ public void testWeakHashMapCacheCleanup() throws Exception { System.out.println("Cache size after GC: " + currentCacheSize + " (initial: " + initialCacheSize + ", attempts: " + attempts + ")"); - // 验证WeakHashMap的清理机制工作正常 + // Verify that WeakHashMap cleanup mechanism works properly Assert.assertTrue("WeakHashMap should clean up entries or attempts should be reasonable", currentCacheSize <= initialCacheSize + 1 && attempts < 20); } @Test public void testBootstrapClassLoaderHandling() throws Exception { - // 获取Bootstrap ClassLoader缓存字段 + // Get Bootstrap ClassLoader cache field Field bootstrapCacheField = SWDescriptionStrategy.SWTypeDescriptionWrapper.class .getDeclaredField("BOOTSTRAP_TYPE_CACHE"); bootstrapCacheField.setAccessible(true); @@ -109,20 +109,20 @@ public void testBootstrapClassLoaderHandling() throws Exception { int initialBootstrapCacheSize = bootstrapCache.size(); - // 测试Bootstrap ClassLoader (null) 的处理 + // Test Bootstrap ClassLoader (null) handling String testTypeName = "test.BootstrapClass"; SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper = new SWDescriptionStrategy.SWTypeDescriptionWrapper( null, "test", null, testTypeName); - // 通过反射调用getTypeCache方法 + // Call getTypeCache method via reflection Method getTypeCacheMethod = wrapper.getClass() .getDeclaredMethod("getTypeCache"); getTypeCacheMethod.setAccessible(true); SWDescriptionStrategy.TypeCache typeCache = (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper); - // 验证Bootstrap ClassLoader的缓存处理 + // Verify Bootstrap ClassLoader cache handling Assert.assertNotNull("TypeCache should be created for bootstrap classloader", typeCache); Assert.assertTrue("Bootstrap cache should contain the type", bootstrapCache.containsKey(testTypeName)); @@ -141,12 +141,12 @@ public void testMultipleClassLoadersIndependentCache() throws Exception { int initialCacheSize = cache.size(); - // 创建两个不同的ClassLoader + // Create two different ClassLoaders URLClassLoader classLoader1 = new URLClassLoader(new URL[0], null); URLClassLoader classLoader2 = new URLClassLoader(new URL[0], null); String testTypeName = "com.test.SameClassName"; - // 为两个ClassLoader创建相同类名的TypeCache + // Create TypeCache with same class name for both ClassLoaders SWDescriptionStrategy.SWTypeDescriptionWrapper wrapper1 = new SWDescriptionStrategy.SWTypeDescriptionWrapper( null, "test", classLoader1, testTypeName); @@ -154,7 +154,7 @@ public void testMultipleClassLoadersIndependentCache() throws Exception { new SWDescriptionStrategy.SWTypeDescriptionWrapper( null, "test", classLoader2, testTypeName); - // 通过反射调用getTypeCache方法 + // Call getTypeCache method via reflection Method getTypeCacheMethod = SWDescriptionStrategy.SWTypeDescriptionWrapper.class.getDeclaredMethod("getTypeCache"); getTypeCacheMethod.setAccessible(true); @@ -164,18 +164,18 @@ public void testMultipleClassLoadersIndependentCache() throws Exception { SWDescriptionStrategy.TypeCache typeCache2 = (SWDescriptionStrategy.TypeCache) getTypeCacheMethod.invoke(wrapper2); - // 验证两个ClassLoader有独立的缓存项 + // Verify that the two ClassLoaders have independent cache entries Assert.assertNotNull("TypeCache1 should be created", typeCache1); Assert.assertNotNull("TypeCache2 should be created", typeCache2); Assert.assertNotSame("TypeCaches should be different instances", typeCache1, typeCache2); - // 验证缓存结构 + // Verify cache structure Assert.assertEquals("Cache should contain both classloaders", initialCacheSize + 2, cache.size()); Assert.assertTrue("Cache should contain classloader1", cache.containsKey(classLoader1)); Assert.assertTrue("Cache should contain classloader2", cache.containsKey(classLoader2)); - // 验证每个ClassLoader有独立的类型缓存 + // Verify each ClassLoader has independent type cache Map typeCacheMap1 = cache.get(classLoader1); Map typeCacheMap2 = cache.get(classLoader2); @@ -191,14 +191,14 @@ public void testMultipleClassLoadersIndependentCache() throws Exception { @Test public void testConcurrentAccess() throws Exception { - // 测试并发访问场景 + // Test concurrent access scenario final String testTypeName = "com.test.ConcurrentClass"; final int threadCount = 10; final Thread[] threads = new Thread[threadCount]; final URLClassLoader[] classLoaders = new URLClassLoader[threadCount]; final SWDescriptionStrategy.TypeCache[] results = new SWDescriptionStrategy.TypeCache[threadCount]; - // 创建多个线程同时访问缓存 + // Create multiple threads to access cache simultaneously for (int i = 0; i < threadCount; i++) { final int index = i; classLoaders[i] = new URLClassLoader(new URL[0], null); @@ -219,17 +219,17 @@ public void testConcurrentAccess() throws Exception { }); } - // 启动所有线程 + // Start all threads for (Thread thread : threads) { thread.start(); } - // 等待所有线程完成 + // Wait for all threads to complete for (Thread thread : threads) { - thread.join(1000); // 最多等待1秒 + thread.join(1000); // Wait at most 1 second } - // 验证所有结果 + // Verify all results for (int i = 0; i < threadCount; i++) { Assert.assertNotNull("Result " + i + " should not be null", results[i]); } From 0f55b65edaeb7b540bf4f23da44b65aee7029719 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Mon, 15 Sep 2025 16:34:05 +0800 Subject: [PATCH 4/4] add changes --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index f28bb6123e..731f74226a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Release Notes. * Bump up apache parent pom to v35. * Update Maven to 3.6.3 in mvnw. * Fix OOM due to too many span logs. +* Fix ClassLoader cache OOM issue with WeakHashMap. All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/242?closed=1)