java - Java 中可能的 native 内存释放错误

标签 java memory-leaks

[这适用于单线程]

编辑:测试在 Windows 8 上通过; 在 Ubuntu 14.04 上始终失败

编辑 2:目前的想法是,这是获取正确内存使用信息的 *nix 相关问题。

我正在寻找有关堆栈溢出的专家的一些确认,这确实是一个问题,而且我不是在想象它。

我一直在使用 Unsafe 在 Java 中处理 alloc/dealloc 内存。我发现了一些非常奇怪的行为,无法理解我在做什么而不释放内存。我所做的是进行 Vanilla 测试,它不使用任何隐藏的 API 来显示问题。似乎 Unsafe.releaseMemory 以及 VM 和 OS 内存指针之间的底层转换在多线程中失败。

当程序启动时,您需要查看第一行中的 PID,并使用“top -p pid”在终端中打开 TOP。初始RES内存应该在30M左右。如果 VM 导致了问题,那么它最终会拥有比这更多的内存。

输出将如下所示:

31037@ubuntu-dev  Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 25.31-b07
Linux amd64 3.16.0-30-generic
Press any key to start

Tester 4 remaining 49
Tester 3 remaining 49
Tester 2 remaining 49
Tester 1 remaining 49
Tester 3 remaining 48
Tester 4 remaining 48
Tester 2 remaining 48
Tester 1 remaining 48

TOP应该报告这样的信息。你可以看到内存泄漏。检查 BufferPool MX Bean 将显示 Java 认为分配了 0 个字节。

jon@ubuntu-dev:~$ top -d 1 -p 31067 | grep java
 31067 jon       20   0 6847648  27988  15420 S   0.0  0.2   0:00.09 java       
 31067 jon       20   0 7769264 743952  15548 S 315.5  4.6   0:03.25 java       
 31067 jon       20   0 7900336 847868  15548 S 380.1  5.3   0:07.06 java       
 31067 jon       20   0 7834800 810324  15548 S 379.1  5.0   0:10.86 java       
 31067 jon       20   0 7703728 700028  15548 S 379.2  4.3   0:14.66 java       
 31067 jon       20   0 7900336 894940  15548 S 379.2  5.5   0:18.46 java       
 31067 jon       20   0 7703728 674400  15548 S 277.5  4.2   0:21.24 java       
 31067 jon       20   0 7376048 430868  15548 S  59.9  2.7   0:21.84 java       
 31067 jon       20   0 7376048 430868  15548 S   0.0  2.7   0:21.84 java       
 31067 jon       20   0 7376048 430868  15548 S   1.0  2.7   0:21.85 java       
 31067 jon       20   0 7376048 430868  15548 S   0.0  2.7   0:21.85 java       
 31067 jon       20   0 7376048 430868  15548 S   1.0  2.7   0:21.86 java   

这是类。您可以直接调用 cleaner() 或使用 q.poll() 并让 System.gc() 尝试清理它。这不会每次都显示问题,但大多数时候。

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Queue;

import sun.nio.ch.DirectBuffer;

public class VanillaMemoryTest
{
    public static void main(String[] args)
    {
        RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
        OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();

        System.out.println(runtime.getName() + "  "+ runtime.getVmVendor() + " " + runtime.getVmName() + " "+ runtime.getVmVersion());
        System.out.println(os.getName() + " " + os.getArch() + " " + os.getVersion() );

        System.out.println("Press any key to start");

        try
        {
            System.in.read();
        }
        catch (IOException e1)
        {

        }

        Thread one = new Thread(new Tester());
        one.setName("Tester 1");
        Thread two = new Thread(new Tester());
        two.setName("Tester 2");
        Thread three = new Thread(new Tester());
        three.setName("Tester 3");
        Thread four = new Thread(new Tester());
        four.setName("Tester 4");

        one.start();
        two.start();
        three.start();
        four.start();

        try
        {
            four.join();
        }
        catch (InterruptedException e)
        {

        }

        System.out.println("Press any key to exit");

        try
        {
            System.in.read();
        }
        catch (IOException e1)
        {

        }

    }

    private static class Tester implements Runnable
    {
        public void run()
        {
            try
            {
                Queue<ByteBuffer> q = new ArrayDeque<ByteBuffer>();

                int total = 50;

                while(total > 0)
                {
                    try
                    {
                        for (int x = 0; x < 10; x++)
                        {
                            ByteBuffer b;
                            b = ByteBuffer.allocateDirect(1000 * 1000 * 30);
                            q.offer(b);
                        }
                    }
                    catch (Throwable e)
                    {
                        e.printStackTrace();
                    }

                    while (q.size() > 0)
                    {
                        //q.poll();
                        ((DirectBuffer) q.poll()).cleaner().clean();
                    }

                    System.out.println(Thread.currentThread().getName() + " remaining " + (--total));
                }
            }
            catch (Throwable p)
            {
                p.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + " exit");

            System.gc();
        }
    }

}

** 更新,这里是一些热点源代码 **

由于昨天看了一大堆Hotspot的源码,所以给大家画了一张图。

重要的是要了解 Java 总是使用 Zeros 来调节新的内存分配;所以它应该显示在 RES 中。

Unsafe.freeMemory native C 代码。 addr_from_java 什么都不做。

UNSAFE_ENTRY(void, Unsafe_FreeMemory(JNIEnv *env, jobject unsafe, jlong addr))
  UnsafeWrapper("Unsafe_FreeMemory");
  void* p = addr_from_java(addr);
  if (p == NULL) {
    return;
  }
  os::free(p);
UNSAFE_END

调用 os:free(注意#ifdef ASSERT)否则它将是对原生 C::free() 方法的原始调用。我将假设 ASSERT 在 VM 的公共(public)生产版本中为假。这给我们留下了 MemTraker::record_free 这可能是一个问题,或者 Ubuntu 可能只是失去了释放内存的能力。哈哈

void  os::free(void *memblock, MEMFLAGS memflags) {
  NOT_PRODUCT(inc_stat_counter(&num_frees, 1));
#ifdef ASSERT
  if (memblock == NULL) return;
  if ((intptr_t)memblock == (intptr_t)MallocCatchPtr) {
    if (tty != NULL) tty->print_cr("os::free caught " PTR_FORMAT, memblock);
    breakpoint();
  }
  verify_block(memblock);
  NOT_PRODUCT(if (MallocVerifyInterval > 0) check_heap());
  // Added by detlefs.
  if (MallocCushion) {
    u_char* ptr = (u_char*)memblock - space_before;
    for (u_char* p = ptr; p < ptr + MallocCushion; p++) {
      guarantee(*p == badResourceValue,
                "Thing freed should be malloc result.");
      *p = (u_char)freeBlockPad;
    }
    size_t size = get_size(memblock);
    inc_stat_counter(&free_bytes, size);
    u_char* end = ptr + space_before + size;
    for (u_char* q = end; q < end + MallocCushion; q++) {
      guarantee(*q == badResourceValue,
                "Thing freed should be malloc result.");
      *q = (u_char)freeBlockPad;
    }
    if (PrintMalloc && tty != NULL)
      fprintf(stderr, "os::free " SIZE_FORMAT " bytes --> " PTR_FORMAT "\n", size, (uintptr_t)memblock);
  } else if (PrintMalloc && tty != NULL) {
    // tty->print_cr("os::free %p", memblock);
    fprintf(stderr, "os::free " PTR_FORMAT "\n", (uintptr_t)memblock);
  }
#endif
  MemTracker::record_free((address)memblock, memflags);

  ::free((char*)memblock - space_before);
}

最佳答案

RES 值没有下降的事实并不是内存泄漏的证据。可能只是 JVM 已将空间释放回其堆外内存分配器,它已被放置在空闲列表中。它可以从空闲列表分配给您的应用程序……例如,下次它创建内存映射缓冲区时。

JVM 没有义务将内存还给操作系统。

如果您想表明存在内存泄漏,请更改应用程序,使其在无限循环中执行其正在执行的操作。如果它最终因 OOME 而崩溃,那么您就有明显的内存泄漏证据。

关于java - Java 中可能的 native 内存释放错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28523388/

相关文章:

perl - 关于 Perl 中内存利用率的问题

c - Realloc 在字符串调整大小函数中失败

java - OkHttp:避免泄漏连接警告

java - Android - svn 上的元数据文件夹

java - MouseListener 第一次不起作用

java - 从 Activity 导航到 TabLayout/ViewPager fragment

java - Tomcat 中 JNDI 的 Java Mail API 配置文档

java - org.xml.sax.SAXParseException; 1 字节 UTF-8 序列的字节 1 无效

java - 从垃圾收集日志确定内存泄漏

memory-management - Go(lang) 内存使用 : RSIZE growing and VSIZE of 139GB?