Java中可能的本机内存释放错误

时间:2015-02-15 05:46:07

标签: 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将显示分配了0个字节的Java THINKS。

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始终使用零来调整新的内存分配;所以它应该在RES中显示。

Unsafe.freeMemory本机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()方法的原始调用。我将假设在VM的公共生产版本中ASSERT是错误的。这给我们留下了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);
}

2 个答案:

答案 0 :(得分:4)

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

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

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

答案 1 :(得分:2)

这对我来说就像一个错误的基准。一些要点:

您分配的内存量非常小。您通过top在进程内存中看到的效果可能是由JVM内部的其他活动引起的,例如:来自JIT。

操作系统有不同的策略如何提供已分配的内存。一个unix系统可能会给你一个内存指针,并会在第一页错误后逐页分配内存。由于你没有在缓冲区写东西,我怀疑是否真的分配了一些内存。

如果有的话,System.gc()在一次通话中清理所有内容是不可靠的。我的实验表明在第四次调用中有一些更清理的顺序。但是,这对GC专家来说是一件事......