2013-05-01 4 views
9

Это очень простой вопрос. Я сформулирую его с помощью C++ и Java, но он не зависит от языка. Рассмотрим хорошо известную проблему в C++:Сбор мусора против ручного управления памятью

struct Obj 
{ 
    boost::shared_ptr<Obj> m_field; 
}; 

{ 
    boost::shared_ptr<Obj> obj1(new Obj); 
    boost::shared_ptr<Obj> obj2(new Obj); 
    obj1->m_field = obj2; 
    obj2->m_field = obj1; 
} 

Это утечка памяти, и все это знают :). Решение также хорошо известно: нужно использовать слабые указатели, чтобы сломать «refcount interlocking». Известно также, что эта проблема не может быть решена автоматически в принципе. Ответственность за ее решение лежит исключительно на программистах.

Но есть положительная вещь: программист имеет полный контроль над значениями пересчета. Я могу приостановить свою программу в отладчике и проверить refcount для obj1, obj2 и понять, что есть проблема. Я также могу установить точку останова в деструкторе объекта и наблюдать момент разрушения (или выяснить, что объект не был уничтожен).

Мой вопрос касается Java, C#, ActionScript и других языков «Сбор мусора». Я мог бы быть что-то не хватает, но на мой взгляд они

  1. Не дай мне изучить RefCount объектов
  2. Не дайте мне знать, когда объект будет уничтожен (хорошо, когда объект подвергается GC)

Я часто слышу, что эти языки просто не позволяют программисту утечка памяти, и именно поэтому они великолепны. Насколько я понимаю, они просто скрывают проблемы управления памятью и затрудняют их решение.

Наконец, сами вопросы:

Java:

public class Obj 
{ 
    public Obj m_field; 
} 

{ 
    Obj obj1 = new Obj(); 
    Obj obj2 = new Obj(); 
    obj1.m_field = obj2; 
    obj2.m_field = obj1; 
} 
  1. ли утечка памяти?
  2. Если да: как его обнаружить и исправить?
  3. Если нет: зачем?
+1

Это не утечка памяти. Это не защищает ** вас от утечки памяти, но нет ничего, что помешало бы вам освободить эти объекты в деструкторе. Управление памятью является частью ** приложения ** дизайна; низкоуровневые хаки не компенсируют недостаток дизайна. –

+0

не позволяют программисту протечь память не так, но эти langauges могут защитить вас от утечки памяти в большинстве случаев. Это большое преимущество для тех программистов, которые не имеют представления о памяти, мы по крайней мере не нужно слишком беспокоиться об утечке памяти при назначении им некоторых небольших проектов. – StereoMatching

+0

Нет доступа к подсчетам, поскольку большинство реализаций не поддерживают пересчеты, а языки в целом стараются не налагать ограничений на детали реализации, чем это абсолютно необходимо (как это предотвращает лучшие реализации - быстрее, надежнее, удобнее и т. д.). – delnan

ответ

8

Управляемые системы памяти основаны на предположении, что вы не хотите отслеживать проблему утечки памяти в первую очередь. Вместо того, чтобы упростить их решение, вы пытаетесь убедиться, что они никогда не произойдут в первую очередь.

У Java есть потерянный термин для «Memory Leak», что означает любой рост памяти, который может повлиять на ваше приложение, но никогда не бывает, что управляемая память не может очистить всю память.

JVM не используют подсчет ссылок по ряду причин

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

В то время как JLS не запрещает использование ссылочных отсчетов, он не используется ни в одном JVM AFAIK.

Вместо этого Java отслеживает ряд корневых контекстов (например, каждый поток стека) и может отслеживать, какие объекты должны храниться и которые можно отбросить на основе того, могут ли эти объекты достижимы. Он также обеспечивает средство для слабых ссылок (которые сохраняются до тех пор, пока объекты не очищаются) и мягкие ссылки (которые обычно не очищаются, но могут быть у усвоения мусора)

5

AFAIK, Java GC работает, начиная с набора четко определенных начальных ссылок и вычисляя транзитивное замыкание объектов, которые могут быть достигнуты из этих ссылок. Все, что недоступно, «просочилось» и может быть GC-ed.

+0

Возможно, они хорошо определены для Java, но не для меня :). Где я могу узнать больше об этих ссылках? – Nick

+0

@Nick Я не работаю на Java, поэтому, к сожалению, не знаю. Попробуйте документацию Java или дядя Google. – Angew

+0

Основная ссылка на сборку мусора в целом - [_The Garbage Collection Handbook_] (http://gchandbook.org/) Ричарда Джонса, Энтони Хоскинга и Элиот Мосс. В нем обсуждается большинство, если не все, различных стратегий управления памятью (включая подсчет ссылок, используемый shared_ptr). –

1

Важнейшим отличием является то, что в Java и т. Д. вы вообще не участвуете в проблеме удаления. Это может показаться довольно страшным положением, но это удивительно расширяет возможности. Все решения, которые вы использовали, чтобы сделать, кто несет ответственность за удаление созданного объекта, исчезли.

Это действительно имеет смысл. Система знает гораздо больше о том, что доступно, а что нет, чем вы. Он также может принимать более гибкие и разумные решения о том, когда разрушать структуры и т. Д.

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

Как программист ex C, перемещенный на Java, я чувствую вашу боль.

Re - ваш последний вопрос - это не утечка памяти.Когда GC ударяет по , все отбрасывается, за исключением того, что доступно. В этом случае, предположив, что вы выпустили obj1 и obj2, ни одна из них не доступна, поэтому они оба будут отброшены.

+0

«Система знает гораздо больше о том, что доступно, а что нет, чем вы». Хммм ... Звучит неплохо, пока у меня нет проблем с памятью. Но что мне делать, когда у моей программы не хватает памяти? Я знаю, как исследовать и оптимизировать манекен памяти в среде «C++ like». Но что мне делать в Java? – Nick

+0

@Nick В Java вы должны либо a) увеличить объем памяти, либо b) оптимизировать свою программу, чтобы использовать меньше памяти. Вы можете использовать профилировщик памяти в любом случае, чтобы определить, какое наилучшее решение. Java поставляется со свободным VisualVM, который можно подключить к любому запущенному процессу, и хотя он невелик, это хорошее место для начала. Оптимизация использования памяти - это Java: a) проще, так как многие из проблем на C++ просто не происходят или b) намного сложнее, потому что у вас не так много способов сжать каждый последний байт вашей системы. Учитывая, что вы покупаете 32 ГБ за 300 долларов, я поеду с опцией.) –

1

Сбор мусора не является простой ref подсчет.

Цифрный пример ссылки, который вы демонстрируете, не будет отображаться на управляемом языке, собранном с мусором, потому что сборщик мусора захочет трассировать ссылки на выделение, обращаясь к чему-то в стеке. Если нет ссылки на стек, это мусор. Системы подсчета числа, такие как shared_ptr, не настолько умны, и это возможно (как вы демонстрируете) иметь два объекта где-то в куче, которые не позволяют друг другу удаляться.

0

Собранные мусором языки не позволяют вам проверять refcounter, потому что у них нет никого. Сбор мусора - это совсем другое дело, связанное с восстановлением памяти. Реальное различие заключается в детерминизме.

{ 
std::fstream file("example.txt"); 
// do something with file 
} 
// ... later on 
{ 
std::fstream file("example.txt"); 
// do something else with file 
} 

в C++ у вас есть гарантия, что example.txt был закрыт после того, как первый блок закрыт, или если исключение. Предотвращение его с помощью Java

{ 
try 
    { 
    FileInputStream file = new FileInputStream("example.txt"); 
    // do something with file 
    } 
finally 
    { 
    if(file != null) 
    file.close(); 
    } 
} 
// ..later on 
{ 
try 
    { 
    FileInputStream file = new FileInputStream("example.txt"); 
    // do something with file 
    } 
finally 
    { 
    if(file != null) 
    file.close(); 
    } 
} 

Как вы видите, вы использовали управление памятью для управления всеми другими ресурсами. Это реальная разница, refcounted objects все еще сохраняют детерминированное разрушение. В языках сбора мусора вы должны вручную освободить ресурсы и проверить исключение. Можно утверждать, что явное управление памятью может быть утомительным и подверженным ошибкам, но в современном C++ вы его смягчаете с помощью интеллектуальных указателей и стандартных контейнеров. У вас все еще есть некоторые обязанности (например, круговые ссылки), но подумайте о том, сколько блоков catch/finally вы можете избежать детерминированного уничтожения и сколько набирать Java/C#/и т. Д.программист должен сделать вместо этого (поскольку они должны вручную закрыть/освободить ресурсы, кроме памяти). И я знаю, что в C# используется синтаксис (и что-то подобное в новейшей Java), но он охватывает только срок действия блока, а не более общую проблему совместного использования.

2

Java обладает уникальной стратегией управления памятью. Все (кроме нескольких конкретных вещей) выделяется на кучу и не освобождается, пока GC не начнет работать.

Например:

public class Obj { 
    public Object example; 
    public Obj m_field; 
} 

public static void main(String[] args) { 
    int lastPrime = 2; 
    while (true) { 
     Obj obj1 = new Obj(); 
     Obj obj2 = new Obj(); 
     obj1.example = new Object(); 
     obj1.m_field = obj2; 
     obj2.m_field = obj1; 
     int prime = lastPrime++; 
     while (!isPrime(prime)) { 
      prime++; 
     } 
     lastPrime = prime; 
     System.out.println("Found a prime: " + prime); 
    } 
} 

C обрабатывает эту ситуацию, требуя, чтобы вручную освободить память как «OBJ» и C++ подсчитывает ссылки на «OBJ» и автоматически удаляет их, когда они выходят из области видимости , Ява не освободите эту память, по крайней мере, не поначалу.

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

Допустим, сборщик мусора java решил очистить после 10 000-й итерации внешнего контура. К этому времени создано 10 000 объектов (которые уже были бы освобождены в C/C++).

Несмотря на то, что существует 10000 итераций внешнего цикла, только код созданного объекта obj1 и obj2 может быть указан кодом.

Это корни GC, которые java использует для поиска всех объектов, на которые возможно ссылаться. Затем сборщик мусора рекурсивно выполняет итерацию по дереву объектов, отмечая «пример» как активный в зависимости от корней сборщика мусора.

Все эти другие объекты затем уничтожаются сборщиком мусора. Это действительно связано с штрафом за производительность, но этот процесс был сильно оптимизирован и не имеет большого значения для большинства приложений.

В отличие от C++, вам не нужно беспокоиться о ссылочных циклах вообще, поскольку будут жить только объекты, доступные из корней GC.

С java-приложениями вам do нужно беспокоиться о памяти (подумайте о списках, удерживающих объекты от всех итераций), но это не так важно, как другие языки.

Как для отладки: идея в Java отладки высоких значений памяти используется специальный «память-анализатор», чтобы выяснить, какие объекты все еще на куче, не беспокоиться о том, что ссылки на что.

+4

Стратегия управления памятью Java вряд ли уникальна. Сбор мусора восходит к Лиспе. Кроме того, разные JVM могут реализовать сборку мусора по-разному. –

+0

@JayElston Lisp изобрел сбор мусора? И я знаю, что это упрощение, у JVM есть много возможностей для того, чтобы собрать crud :) – Techcable

+0

Если вы считаете [Wikipedia] (https://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29) :-) –

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