[这适用于单线程]
编辑: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);
}
答案 0 :(得分:4)
RES
值不会丢失的事实不是内存泄漏的证据。可能只是JVM已经将空间释放回其堆外内存分配器,并将其放置在空闲列表中。它可以从空闲列表分配给您的应用程序......例如,下次它创建一个内存映射缓冲区时。
JVM没有义务将内存返回给操作系统。
如果要显示内存泄漏,请更改应用程序,使其在无限循环中执行正在执行的操作。如果它最终与OOME崩溃,那么你就有明显的内存泄漏证据。
答案 1 :(得分:2)
这对我来说就像一个错误的基准。一些要点:
您分配的内存量非常小。您通过top在进程内存中看到的效果可能是由JVM内部的其他活动引起的,例如:来自JIT。
操作系统有不同的策略如何提供已分配的内存。一个unix系统可能会给你一个内存指针,并会在第一页错误后逐页分配内存。由于你没有在缓冲区写东西,我怀疑是否真的分配了一些内存。
如果有的话,System.gc()在一次通话中清理所有内容是不可靠的。我的实验表明在第四次调用中有一些更清理的顺序。但是,这对GC专家来说是一件事......