Skip to content

[BUG] <may MemorySafeWindowTinyLFUMap cause a memory leak> #4475

Description

@xuyicheng1995

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

memory leaked

Expected Behavior

when i use System.gc(), no memory leaked

Steps To Reproduce

public class TestMemoryLeak {
// 1m内存
private static final int M_1 = 1 * 1024 * 1024;

public static void main(String[] args) throws InterruptedException, IOException {
    System.in.read();
    try {
        mswtMapTest();
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.gc();
    TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
}

public static void mswtMapTest() {
    for (int i = 0; i < 10; i++) {
        System.gc();
        MemorySafeWindowTinyLFUMap<String, Byte[]> map = new MemorySafeWindowTinyLFUMap<>(1, 100);
        map.put(String.valueOf(i), new Byte[M_1 * 2]);
        // 手动GC
    }
}

public static void commonMapTest() {
    for (int i = 0; i < 10; i++) {
        System.gc();
        Map<String, Byte[]> map = new HashMap<>();
        map.put(String.valueOf(i), new Byte[M_1 * 2]);
        // 手动GC
        System.gc();
    }
}

}

Environment

jvm: -Xms20m -Xmx20m

ShenYu version(s):

Debug logs

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at org.apache.shenyu.common.cache.TestMemoryLeak.mswtMapTest(TestMemoryLeak.java:44)
at org.apache.shenyu.common.cache.TestMemoryLeak.main(TestMemoryLeak.java:32)

Anything else?

MemorySafeWindowTinyLFUMap hold a static reference ALL, every reference of MemorySafeWindowTinyLFUMap will be always hold by the reference, and may cause a memory leak

Activity

  1. yu199195 commented on Mar 15, 2023

    @yu199195
    Member

    @xuyicheng1995 hi, can you fix it?

  2. xuyicheng1995 commented on Mar 15, 2023

    @xuyicheng1995
    ContributorAuthor

    @xuyicheng1995 hi, can you fix it?

    ok i can have a try

  3. moremind commented on Mar 16, 2023

    @moremind
    Member

    Caffeine won't catch OutOfMemoryError in order to suppress and evict entries. When that error is thrown the JVM is left in an unpredictable state, which may not be recoverable. It is best to use -XX:+ExitOnOutOfMemoryError and -XX:+HeapDumpOnOutOfMemoryError to restart and investigate the memory leak offline.

    At first it might seem beneficial to defer all of the available memory to the caches and let the garbage collector evict when needed. This is the idea behind soft references. Unfortunately, that approach has been troublesome due to making full GCs more frequent and increasing their stop-the-world pause times. We instead recommend using an explicit maximum size, where the policy is quite intelligent. If you want large caches then usually a remote tier, like memcached or redis, is a good compliment so you get the speed of local access and a lower miss penalty on cache loads.

    pls refer to:https://stackoverflow.com/questions/71401507/what-happens-when-caffeine-runs-out-of-memory

  4. xuyicheng1995 commented on Mar 16, 2023

    @xuyicheng1995
    ContributorAuthor

    Caffeine won't catch OutOfMemoryError in order to suppress and evict entries. When that error is thrown the JVM is left in an unpredictable state, which may not be recoverable. It is best to use -XX:+ExitOnOutOfMemoryError and -XX:+HeapDumpOnOutOfMemoryError to restart and investigate the memory leak offline.

    At first it might seem beneficial to defer all of the available memory to the caches and let the garbage collector evict when needed. This is the idea behind soft references. Unfortunately, that approach has been troublesome due to making full GCs more frequent and increasing their stop-the-world pause times. We instead recommend using an explicit maximum size, where the policy is quite intelligent. If you want large caches then usually a remote tier, like memcached or redis, is a good compliment so you get the speed of local access and a lower miss penalty on cache loads.

    pls refer to:https://stackoverflow.com/questions/71401507/what-happens-when-caffeine-runs-out-of-memory

    sry, i think your reply has no relation with the question.

  5. moremind commented on Mar 16, 2023

    @moremind
    Member

    org.apache.shenyu.common.cache.MemorySafeWindowTinyLFUMap#ALL is not related to oom, you can test it with caffeine, caffeine cannot rely on oom for cache clearing. so, no matter how you modify it, it will oom

  6. moremind commented on Mar 16, 2023

    @moremind
    Member

    this is my test result with caffeine.

    2023-03-16T10:31:49.869+0800: [GC (Allocation Failure) [PSYoungGen: 5632K->480K(6144K)] 5632K->1121K(19968K), 0.0016521 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2023-03-16T10:31:59.081+0800: [GC (Allocation Failure) [PSYoungGen: 4325K->496K(6144K)] 13158K->9675K(19968K), 0.0008055 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2023-03-16T10:31:59.082+0800: [GC (Allocation Failure) [PSYoungGen: 496K->480K(6144K)] 9675K->9715K(19968K), 0.0014429 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2023-03-16T10:31:59.084+0800: [Full GC (Allocation Failure) [PSYoungGen: 480K->0K(6144K)] [ParOldGen: 9235K->9558K(13824K)] 9715K->9558K(19968K), [Metaspace: 5654K->5654K(1056768K)], 0.0115917 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    2023-03-16T10:31:59.095+0800: [GC (Allocation Failure) [PSYoungGen: 0K->0K(6144K)] 9558K->9558K(19968K), 0.0006404 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    2023-03-16T10:31:59.096+0800: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(6144K)] [ParOldGen: 9558K->9470K(13824K)] 9558K->9470K(19968K), [Metaspace: 5654K->5647K(1056768K)], 0.0124208 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
    
    PSYoungGen      total 6144K, used 996K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
      eden space 5632K, 17% used [0x00000000ff980000,0x00000000ffa79108,0x00000000fff00000)
      from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
      to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
     ParOldGen       total 13824K, used 9470K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
      object space 13824K, 68% used [0x00000000fec00000,0x00000000ff53fa18,0x00000000ff980000)
     Metaspace       used 5741K, capacity 5896K, committed 6144K, reserved 1056768K
      class space    used 643K, capacity 706K, committed 768K, reserved 1048576K
    
  7. xuyicheng1995 commented on Mar 16, 2023

    @xuyicheng1995
    ContributorAuthor

    org.apache.shenyu.common.cache.MemorySafeWindowTinyLFUMap#ALL is not related to oom, you can test it with caffeine, caffeine cannot rely on oom for cache clearing. so, no matter how you modify it, it will oom

    when i new a instance of MemorySafeWindowTinyLFUMap, this instance will be hold by org.apache.shenyu.common.cache.MemorySafeWindowTinyLFUMap#ALL and current thread stack variable, i set the current thread variale to null,mean JVM will collect this instance,but this instance still is held by org.apache.shenyu.common.cache.MemorySafeWindowTinyLFUMap#ALL,JVM cannot collect this instance

  8. xuyicheng1995 commented on Mar 16, 2023

    @xuyicheng1995
    ContributorAuthor

    code like this:

    public static void mswtMapTest() {
        for (int i = 0; i < 10; i++) {
            MemorySafeWindowTinyLFUMap<String, Byte[]> map = new MemorySafeWindowTinyLFUMap<>(1, 100);
            map.put(String.valueOf(i), new Byte[M_1 * 2]);
            map = null;
            // 手动GC
        }
    }
    
  9. xuyicheng1995 commented on Mar 16, 2023

    @xuyicheng1995
    ContributorAuthor

    code like this:

    public static void mswtMapTest() {
        for (int i = 0; i < 10; i++) {
            MemorySafeWindowTinyLFUMap<String, Byte[]> map = new MemorySafeWindowTinyLFUMap<>(1, 100);
            map.put(String.valueOf(i), new Byte[M_1 * 2]);
            map = null;
            // 手动GC
        }
    }
    

    i think this code will cause memory leak, your test is about caffine cache, but caffine cache is held by a instance of MemorySafeWindowTinyLFUMap, so if org.apache.shenyu.common.cache.MemorySafeWindowTinyLFUMap#ALL do not remove this instance, gc not works

  10. moremind commented on Mar 16, 2023

    @moremind
    Member

    ce of MemorySafeWindowTinyLFUMap, this ins

    use org.apache.shenyu.common.cache.WindowTinyLFUMap to test, your code is a reference, you set the null, gc will work.

  11. xuyicheng1995 commented on Mar 16, 2023

    @xuyicheng1995
    ContributorAuthor

    ce of MemorySafeWindowTinyLFUMap, this ins

    use org.apache.shenyu.common.cache.WindowTinyLFUMap to test, your code is a reference, you set the null, gc will work.

    i dont understand, why let me use org.apache.shenyu.common.cache.WindowTinyLFUMap to test? what i said is, use MemorySafeWindowTinyLFUMap may cause a memory leak.

  12. yunlongn commented on Mar 16, 2023

    @yunlongn
    Member

    But the MemorySafeWindowTinyLFUMap object is unique in God's Domain @xuyicheng1995

  13. ben-manes commented on Mar 16, 2023

    @ben-manes

    It looks like the request is to use a weak cache for the static ALL field so that instances may be collected when there are no strong references held by the application. As this is a static field that can be called by multiple threads, it should also be thread-safe. It doesn't look like ordering matters, so you could use a Caffeine weakKey cache with Collections.newSetFromMap. Something like,

    private static final Set<MemorySafeWindowTinyLFUMap<?, ?>> ALL = Collections.newSetFromMap(
        Caffeine.newBuilder().weakKeys().<MemorySafeWindowTinyLFUMap<?, ?>>build().asMap());
  14. moremind commented on Mar 16, 2023

    @moremind
    Member

    It looks like the request is to use a weak cache for the static ALL field so that instances may be collected when there are no strong references held by the application. As this is a static field that can be called by multiple threads, it should also be thread-safe. It doesn't look like ordering matters, so you could use a Caffeine weakKey cache with Collections.newSetFromMap. Something like,

    private static final Set<MemorySafeWindowTinyLFUMap<?, ?>> ALL = Collections.newSetFromMap(
        Caffeine.newBuilder().weakKeys().<MemorySafeWindowTinyLFUMap<?, ?>>build().asMap());

    as I know, Caffeine cannot be memory safe,

  15. moremind commented on Mar 16, 2023

    @moremind
    Member

    It looks like the request is to use a weak cache for the static ALL field so that instances may be collected when there are no strong references held by the application. As this is a static field that can be called by multiple threads, it should also be thread-safe. It doesn't look like ordering matters, so you could use a Caffeine weakKey cache with Collections.newSetFromMap. Something like,

    private static final Set<MemorySafeWindowTinyLFUMap<?, ?>> ALL = Collections.newSetFromMap(
        Caffeine.newBuilder().weakKeys().<MemorySafeWindowTinyLFUMap<?, ?>>build().asMap());

    as I know, Caffeine cannot be memory safe,

    or catch Out Of Memory Error to release caffeine cache.

  16. moremind commented on Mar 16, 2023

    @moremind
    Member

    this may be refer to: ben-manes/caffeine#776, achieve memory safe is not easy for jvm.

  17. ben-manes commented on Mar 16, 2023

    @ben-manes

    This is simply allowing the garbage collection of an instance that is no longer reachable by the client, but is held due to a private static field. By using weak references, you allow the instance to be collected once it becomes unused.

  18. xuyicheng1995 commented on Mar 16, 2023

    @xuyicheng1995
    ContributorAuthor

    This is simply allowing the garbage collection of an instance that is no longer reachable by the client, but is held due to a private static field. By using weak references, you allow the instance to be collected once it becomes unused.

    i think that is a good idea, and I originally wanted to learn from Netty`s ReferenceCount class, user retain and release instance ref count, but it is a little diffcult

  19. yu199195 commented on Mar 17, 2023

    @yu199195
    Member

    @ben-manes @xuyicheng1995 Thanks for your suggestions, I think we have solved it~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions