2016-04-16 3 views
0

Так что я недавно попытался реализовать Stack, используя один связанный список в Java, и у меня есть эта проблема. Если я нахожу 100000 элементов в стек естественно, память увеличивается, потому что стек требует 100000 узлов для хранения объектов. Однако, когда стек опустошается (после 100 000 pops), ссылки на эти Узлы и их содержимое должны выпадать из объема, поэтому, если стек заполняет резервную копию, использование памяти не должно расти. Однако, когда это происходит в моем коде, память удваивается. Другими словами, поп, похоже, недостаточно удаляет ссылки или не допускает сбор мусора, и я надеюсь, что кто-то скажет мне, почему.Ошибка утечки памяти Java Stack

Вот класс стека

public class Stack<T> { 
    private Node<T> top; 
    private int size; 
    private long limit; 

    public boolean isEmpty() { 
     return this.size == 0; 
    } 

    public void setStackLimit(long limit) { 
     this.limit = limit; 
    } 

    public int size() { 
     return size; 
    } 
    public Stack() { 
     this(1000L); 
    } 
    public Stack(long limit) { 
     this.size = 0; 
     this.top = null; 
     this.limit = limit; 
    } 
    public void push(T o) throws StackOverflowException{ 
     if (this.size == this.limit) throw new StackOverflowException("The stack overflowed"); 
     if (this.top == null) this.top = new Node<T>(o); 
     else { 
      Node<T> r = new Node<T>(o); 
      Node<T> temp = this.top; 
      this.top = r; 
      this.top.setNext(temp); 
     } 
     this.size++; 
    } 
    @SafeVarargs 
    public final void mpush(T... o) throws StackOverflowException{ 
     for (T ob: o) { 
      this.push(ob); 
     } 
    } 
    public T pop() throws EmptyStackException{ 
     if (this.top == null) throw new EmptyStackException("The stack is empty"); 
     else { 
      T o = this.top.getPayload(); 
      this.top = this.top.getNext(); 
      this.size--; 
      return o; 
     } 
    } 

    public boolean empty() { 
     while (!this.isEmpty()){ 
      try { 
       this.pop(); 
      } 
      catch (Exception e) { 
       return false; 
      } 
     } 
     return true; 
    } 

    public String printStack() { 
     String stack = ""; 
     Node<T> temp = this.top; 
     while (temp != null){ 
      if (temp.getPayload() instanceof Stack<?>) { 
       Stack<?> stack2 = (Stack<?>)temp.getPayload();; 
       stack = "| " + stack2.printStack() + stack; 
      } else 
       stack = "|" + temp.getPayload().toString() + stack; 
      temp = temp.getNext(); 
     } 
     return stack.replaceAll("[| ]$",""); 
    } 

    public T peek() throws EmptyStackException { 
     if (this.top == null) throw new EmptyStackException("The stack is empty"); 
     else { 
      T o = this.top.getPayload(); 
      return o; 
     } 
    } 

    public boolean isFull() { 
     return this.size == this.limit; 
    } 

    public Object[] toArray() { 
     Object[] returnvalue = new Object[this.size]; 
     int index = this.size-1; 
     Node<T> temp = this.top; 
     while (index >= 0) { 
       returnvalue[index--] = temp.getPayload(); 
       temp = temp.getNext(); 
     } 
     return returnvalue; 
    } 

    public String toString() { 
     try { 
      return "<Stack object of size " + this.size() + " last element: " + this.peek().toString() + ">"; 
     } catch (Exception e) { 
      return "<Empty stack object of size " + this.size() + ">"; 
     } 

    } 
} 

и здесь является реализация узла

public class Node<T> { 
    private T payload; 
    private Node<T> next; 
    public Node(T payload) { 
     this.payload = payload; 
    } 
    public Node<T> getNext(){ 
     return this.next; 
    } 
    public boolean hasNext() { 
     return this.next != null; 
    } 
    public T getPayload() { 
     return this.payload; 
    } 
    public void setNext(Node<T> n) { 
     this.next = n; 
    } 
    public void setPayload(T o) { 
     this.payload = o; 
    } 
} 
+0

это нет утечка памяти. узнайте, что означает утечка памяти. это просто потому, что jvm держит вашу память внутри процесса. это не означает, что ваша память потеряна. – HuStmpHrrr

+1

также, в перспективе gc. нет никакого правила для любой реализации gc для сбора мусора, как только они будут созданы. – HuStmpHrrr

+0

Я знаю, что память не освобождается от процесса java, когда ссылка удаляется, но не должна продолжать потреблять память. Если он не возвращает ОЗУ ОС, не должен ли он, по крайней мере, не получить больше памяти после его перезагрузки. –

ответ

0

Вы действительно не должны беспокоиться о таких вещах, если вы не попали в реальную проблему (например, ошибка ООМ) , Когда вы сказали о гигабайте памяти, я заинтересовался, и решил уйти. Я использовал этот код:

Stack<Integer> stack = new Stack<>(100_000_000L); 
System.out.println(Runtime.getRuntime().maxMemory()); 
for (int j = 0; j < 100; ++j) { 
    System.out.println(j + "th run"); 
    for (int i = 0; i < 10_000_000; ++i) { 
     stack.push(i); 
    } 
    System.out.println(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); 
    Thread.sleep(10000); 
    stack.empty(); 
    System.out.println(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); 
    Thread.sleep(10000); 
} 

(Кстати, в чем смысл иметь long предел но int размера?)

Первая строка печатается 3795845120, который я предполагаю, соответствует что-то вроде 4 Гб (с некоторой памятью, выделяемой для стека, классов, объектов JVM и других очень полезных вещей). Затем он перешел в цикл, и я контролировал оба профилировщика памяти (что для этого использует NetBeans 8.1) и диспетчер задач Windows. Сначала прогонщик сообщил о 10 М объектах/400 МБ памяти, а Windows сообщила о 500 МБ использования памяти. Все идет нормально.

На 4-м запуске это было как 15 М объектов/600 МБ памяти в профилировщике, но около 1 ГБ в диспетчере задач. Тем не менее, не 40 М объектов! Таким образом, GC выполняет свою работу, хотя и медленно.

На 8-м прогоне профайлер сообщил о 25 М объектах, и память выросла до 1700 МБ. Но на 9-м запуске он вернулся к 15 М объектам и остался там. Память, о которой сообщает диспетчер задач, застряла в 1700 МБ и больше не увеличивалась.

В другом месте, код напечатан

0th run 
406432424 
408400384 
1th run 
408267736 
409295992 
2th run 
602312032 
603027096 
3th run 
620573368 
621308040 
4th run 
614213120 
615099616 
5th run 
616810512 
617419624 
6th run 
615366040 
616072952 
7th run 
620580088 
621587032 
8th run 
1020848024 
1022640736 
9th run 
627436904 
628500048 

Вы можете ясно видеть GC работает.

Из этого эксперимента я догадался, что Java пытается сохранить потребление памяти в пределах 50% от максимального предела, который он дал. Мой ящик имеет 16 ГБ или оперативную память, поэтому я думаю, поэтому предел по умолчанию - это высокий (он даже использует VM сервера по умолчанию). Ниже 50% GC все еще выполняет свою работу, только память не возвращается обратно в ОС.

Чтобы проверить эту гипотезу, я использовал тот же код с -Xmx 1000M. Результат был немного удивителен, однако: он оставался примерно в 950M в соответствии с диспетчером задач. Это означает, что алгоритм не так прост, как только 50%. Вероятно, это что-то умное и оптимизированное для оборудования и среды.

Тем не менее, мы можем с уверенностью заключить, что GC работает правильно (удивление!). Кстати, я действительно думаю, что вы должны были сделать что-то вроде моих нескольких строк кода до, разместив вопрос, чтобы показать некоторые исследовательские работы. Не похоже, чтобы это было где-то тяжело или требовало специальных навыков.Тогда вопрос скорее всего был бы похож на “What is the memory management strategy of the JVM?”, googling для которого показывает много интересных ссылок.

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