2015-02-15 3 views
4

[Это работает правильно однопоточным]Возможной ошибка нативного освобождения памяти в Java

EDIT: Тестирование проходит на Windows 8; Не удается последовательно на Ubuntu 14.04

EDIT 2: Текущее мышление, что это *, связанные с Никс вопрос о получении надлежащей информации об использовании памяти.

Я ищу подтверждение от гуру здесь о переполнении стека, что это действительно проблема и что я не представляю ее.

Я работал с памятью alloc/dealloc в Java, используя Unsafe. Я нашел какое-то действительно странное поведение и не мог понять, что делаю, чтобы не выпускать память. Я сделал тест Vanilla, который не использует скрытые API, чтобы показать проблему. Кажется, что Unsafe.releaseMemory и базовый перевод между указателями памяти VM и OS терпят неудачу при многопоточности.

Когда программа запускается, вам нужно посмотреть PID в первой строке и открыть TOP в терминале, используя «top -p pid». Первоначальная память RES должна составлять около 30M. Если виртуальная машина вызывает проблему, она будет иметь гораздо больше памяти.

Результат будет выглядеть так:

[email protected] 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 THINKS выделяет 0 байт.

[email protected]:~$ 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(); 
     } 
    } 

} 

** UPDATE, вот некоторые HOTSPOT ИСХОДНЫЙ КОД **

Поскольку я потратил кучу времени вчера, глядя на исходный код Hotspot ... Я нарисую каждому картину.

Важно понимать, что Java всегда устанавливает новые распределения памяти с нулями; поэтому он должен отображаться в ВИЭ.

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 

вызовы ОС: бесплатно (уведомление #ifdef Assert) в противном случае было бы сырым вызов родной C :: бесплатно() метод. Я собираюсь предположить, что ASSERT является ложным в публичном производстве VM. Это оставляет нам MemTraker :: record_free, что может быть проблемой, или Ubuntu, возможно, просто потерял способность освобождать память. lol

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

Это сообщение об ошибке или есть вопрос где-то? –

+0

В поисках подтверждения толпы. – bond

+0

Попробуйте взглянуть на VM availableMemory вместо "top". * Топ включает память, «обещанную» приложению ОС, даже если она никогда не используется *. Память может быть бесплатной и доступной. –

ответ

2

Это выглядит как ошибочный бенчмарк. Некоторые моменты:

Объем выделенной памяти довольно мал. Эффекты, которые вы видите в памяти процесса сверху, могут быть результатом другой активности внутри JVM, например. от JIT.

Операционные системы имеют разные стратегии распределения выделенной памяти. Система unix может дать вам указатель на память и будет назначать только память за страницей после первой ошибки страницы. Поскольку вы не пишете что-то в буфер, я сомневаюсь, что на самом деле выделена какая-то память.

System.gc() не является надежной очисткой всего на одном звонке, если вообще. Мои эксперименты показали, что на четвертом вызове происходит нечто более чистое. Тем не менее, это что-то для экспертов GC ....

+0

+1 - Все это действительные баллы. Это очень сложно * получить ручку, когда ресурсы памяти фактически освобождены. –

+0

Java по умолчанию в Unsafe.allocateMemory записывает все нули в блок с помощью Unsafe.setMemory. это поведение по умолчанию. – bond

4

То, что значение RES не падает, не свидетельствует об утечке памяти. Просто может быть, что JVM выпустила пространство обратно в свой распределитель памяти без памяти, где он был помещен в свободный список. Он может быть выделен из бесплатного списка в ваше приложение ... в следующий раз он создает буфер с отображением памяти, например.

JVM не обязан возвращать память ОС.

Если вы хотите показать, что есть утечка памяти, измените приложение так, чтобы оно выполняло то, что он делает в бесконечном цикле. Если он в конечном итоге сбой с OOME, то у вас есть четкое свидетельство утечки памяти.

+0

Бесконечный цикл также является хорошим подходом! – cruftex

+0

Он, кажется, не взрывается в бесконечном цикле. Следующий вопрос: поскольку код C буквально вызывает :: free(), тогда как в мире вы можете отслеживать использование памяти, если * nix (ubuntu) не регистрирует свободную память. – bond

+0

Если код не взорвался, нет никаких данных об утечке памяти. (И если этого не существует, то вы не найдете его, посмотрев исходный код JVM!). Задайте свой последующий вопрос как новый вопрос. Он больше не связан с этим. –

Смежные вопросы