Pull to refresh

Comments 80

UFO just landed and posted this here
Не путаю. Утечка памяти — процесс неконтролируемого уменьшения доступной для использования памяти. В приведённых выше случаях при указанных условиях сборщик мусора не соберёт эти объекты.
Проблема утечек памяти в яве довольно не тривиальна и очень запутана. В добавление к этому материалу будет полезно почитать материалы с IBM. Статьи старые, но думаю, что все еще актуальные:
Java theory and practice: Plugging memory leaks with weak references
Java theory and practice: Plugging memory leaks with soft references
Очень полезные источники, спасибо. Они ещё долго будут актуальными.
Про особенность работы со строками не знал. Спасибо!
Автор, советую про про строки добавить бы одно уточнение. Интернированные строки хранятся не в heapspace, они хранятся в permgen space. И сборка мусора в нем происходит по отдельным правилам, не так как в heap-e / young/tenured memory pools.
Спасибо за полезное уточнение, добавил в пост.
Желтоватый заголовок получился. Для этой статьи мне кажется больше подошло бы название вроде «Используйте память экономно» или что-то вроде этого, а то создается впечатление что у джавы где-то в натив коде есть утечки, на которые программист никак не может повлиять. А за статью спасибо. Познавательно
Спасибо за полезную критику, первая статья :) Думаю, что также было бы уместно использовать «Типичные случаи утечки памяти в Java-приложениях» и «при Java-разработке».
Кроме замечения по строкам, я бы добавил к пункту про потоки — если у вас столько много активных потоков, что потребляемый ими стек начинает беспокоить, смотрите в сторону NIO и отделения пула потоков от пула connections/IO streems через channels/selectors.
Можно еще и с размером стека поиграться, дефолтные 512кб это много, иногда даже очень много, тут можно хорошо поиграться с параметром -Xss
Угу, согласен. Но хотя… сколько активных потоков у вас будет? Ну пусть 100, это 50мб оверхеда по памяти. У вас их 2000? Наверное лучше подумать а зачем вам так много.
Например если у меня сервер с архитектурной где на каждого клиента по потоку — там и больше может быть. Некоторые утверждают что это во первых быстрее, во вторых проще для разработчика.

Например www.mailinator.com/tymaPaulMultithreaded.pdf
Ну проще для разработчика-то конечно. Насчет быстрее, я бы сказал зависит от того с какой частотой и в каких объемах приходят данные от каждого клиента (потока). Если там данных условно «часто и много» с точки зрения обработки и нагрузки на процессор — да, наверное один thread/connection и синхронный IO будет лучше, если условно «приходят с перерывами и поток большую часть времени простаивает» — я бы выбрал NIO, хотя для программирования он будет сложнее.
Возможно для такой архитектуры больше подошел бы тот же Erlang с его легковесными процессами и tail-call оптимизацией?
Возможно, но есть очень много но — например найти человека, а то и не одного который бы смог написать это + интегрировать с текущим кодом.

Ту реализацию такого сервера я видел, получилась очень простой и действительно очень быстрой при 300-500 коннектах с хоть и не частым и при этом большим объемом IO и тяжелой бизнес логикой.
У этого Но есть и преимущество — такие люди будут востребованы ещё долго, а людей которые об этом знают ещё мало :)
Не вижу ни одного преимущества. Представьте такую ситуацию, вы являетесь работодателем и делаете какой-то клевый продукт и у вас вся база кода с пятью разработчиками на яве, примерно 80% затрат — это зп сотрудникам. Теперь перед вами встает задача написать к этому продукту сервер, то вам врядли придет в голову идея найти, а главное нанять 2-х человек с зп в полтора раза большей нежели у ваших программистов что бы написать сервер, при этом половину уже реализованного кода придется переписывать на другом языке.

Это самоубийство. Сервер будет писаться силами разработчиков, это круто если кто-то из них в курсе что такое seda и может быстро на той же mina/netty поднять за недельку что-то подобное что нам нужно, а если только парочка из них в курсе что такое сокеты, а уж про конкареннси им надо в гугл ходить…
К сожалению я слабо представляю продукт, к которому неожиданно надо написать сервер — не это ли требование будет самоубийством? Не будет ли это уже новым продуктом?
Если из 5-ти разработчиков 1-2 слышали о concurrency в java, то выбор «нанять-найти» или «использовать-что-есть» далеко не очевиден. Возможно в таком случае дешевле будет сначала «обучить», а потом уже пустить за разработку сервера.
Впрочем, это всё вопросы управления уже готовым продуктом. Я же говорю о том, когда вопрос выбора технологий и подбора команды стоит до начала разработки.
Не вижу ничего желтого в заголовке. Когда только java появилась внимательно присматривался у этому языку и отказался от его использования.
1. Слишком много областей видимости и они порождают хаос в голове разработчика (тем кто на ней пишет задайте себе вопрос, а сколько их?).
2. Нет множественного наследования (разработчики лукавят, что это плохо, просто им это сложно сделать).
3. У нас нет goto, но зато есть break и continue с метками (это просто бред какой-то).
4. Главный принцип языка java — переносимость (а не скорость, хотя все производители про нее только и орут), java везде должна работать одинаково (вынужден ставить по несколько версий, поскольку одни приложения работают под одной, другие под другой, в результате вообще видя написан java использую только при отсутствии альтернативы).

Такая работа с памятью, это просто очередной гвоздь в крышку…. Я конечно понимаю не хотели пускать Microsoft и это правильно, но за счет такого….
Толсто. Но если хотите спорить — давайте продолжим.
Чтобы спорить ответьте аргументированно на мои пункты.
1….
2….
3….
4….

Если грамотно, я смолчу, значит Вы разумный человек, если нет то…. Вообщем ответьте, моего ответа не будет, а я Вашего жду.
1. java.sun.com/docs/books/jls/second_edition/html/names.doc.html#103228 — вы про это, что ли? Но в любом случае, хаос в голове разработчика обычно порождают не они.

2. Это правда. А зачем вам множественное наследование? Есть интерфейсы, абстрактные классы и обычные классы.

3. Строго говоря, ни goto, ни брейки не нужны для полноты языка по тьюрингу (точнее, нужен или гото, или оператор цикла). Смысл в том, что с помощью goto вы могли бы перейти откуда угодно куда угодно, а break/continue работают изнутри циклов, что как бы, делает их менее повсеместными и как бы ограниченными для применения, хотя в определенных случаях их все конечно используют. Я собственно, их не особенно люблю.

4. Переносимость между чем, прежде всего? Между Операционками? Есть. Между архитектурами хардварными? Есть.

У нас сложное серверное приложение работает на Win XP/Vista/7/Redhad/Debian/Solaris, да, по настоящему работает. Про UI-ную часть? Ну тут многие используют нативные фреймворки, типа SWT, как эклипс, но некоторые пишут переносимые и нормально работающие сложные UI приложения на чистом Swing — IntelliJ IDEA, например.

Переносимость между виртуальными машинами? Мм, наверное не очень. Хорошая между Hotspot/JRockit, похуже дело обстоит с apache harmony/gcj, остальные не смотрел.

До тех пор, пока вы не используете недокументированные опции конкретной имплементации JVM, и не используете внутренние API, все переносится нормально. Если используете — да, все к чертям. Да, в сложных проектах довольно часто приходится такие вещи использовать, поэтому переносимость между виртуальными машинами требует доп. усилий.

JVM- абстракция. Как и любая сложная абстракция, она течет (http://www.joelonsoftware.com/articles/LeakyAbstractions.html). Так же, как течет SQL, который вроде бы тоже, по задумке, чисто декларативный язык.

Переносимость между версиями языка? Очень хорошая. До сих пор поддерживается бинарный байткод, скомпилированный для ранних версий JVM, 1.2 например. Где вы такое увидите в .NET, например?

А теперь почему я сказал «толсто», не смотря на то, что по сути многие ваши замечания верны… А альтернатива-то какая? Вы используете написанное на яве в последнюю очередь, отлично, а что вы используете в первую, вторую и третью, и почему?
Второй случай является более правильным, поскольку в большинстве случаев это явно укажет сборщику на сбор неиспользванного экземпляра первоначально созданного HeavyElem.


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

зы. источник тоже укажите, пожалуйста: iwillgetthatjobatgoogle.tumblr.com/post/12591334729/java-memory-leaks
Это мой блог :) Статью изначально писал для хабра, а в блоге перевод на английский язык для его читателей.
Насчёт случая использования — согласен.
Нет, хотя одержим этой идеей. Блог завёл месяц назад и веду его, параллельно освежая знания. Надеюсь, в дальнейшем он станет хорошим источником для подготовки к IT-собеседованиям. Хотя не хотел его освещать тут, внимательный tulskiy случайно выдал его :)
Ыы, сорри что выдал блог :) А вообще статья очень интересная, некоторых фактов не знал, спасибо.
UFO just landed and posted this here
Присоединяюсь к вопросу, весьма интересно.
Скажу честно, данное утверждение увидел в статье «Leaking Memory in Java» по ссылке blog.xebia.com/2007/10/04/leaking-memory-in-java
В посте говорится, что первый случай вызовет OutOfMemoryError.
Цитата про второй случай: «the second creation of the FatObject will trigger a full GC and the GC will be able to clear enough memory (since the old reference has been nulled)».
Согласен с комментарием выше о том, что время между созданиями объектов должно играть роль. Также как и момент, в который будет происходить выполнение этой операции. Конечное поведение зависит от настроек и реализации используемого GC.
Честно сказать и по ссылке не очень-то убедительно.

Will be fine, because the second creation of the FatObject will trigger a full GC and the GC will be able to clear enough memory (since the old reference has been nulled).

Собственно, с чего бы создание нового объекта дергало GC?
Может дергать, если для этого объекта не хватает памяти, вот вам более простой пример, запускать с -XX:+PrintGCDetails -Xmx64m, сборщики можно любые как дефолтные, так и связку ParNew + ConcMarkSweepGC:

    static class Foo {

        byte[] b = new byte[32*1024*1024];
    }

    public static void main(String[] args) throws Exception {

        for(int i = 0; i < 3; i++) {
            Foo f = new Foo();
        }
    }


В логах должны быть 3 красивые записи о сборке мусора.

А теперь читаем про DoEscapeAnalysis.

public class TestMem {
public static void main(String[] args) throws Exception {
System.err.println("create 1");
Test2 e = new Test2();

e = null;

System.err.println("create 2");
e = new Test2();
}

public static class Test2 {
private char mas[] = new char[50000000];
}
}


размер mas подобрать под собственные настройки jvm, и в зависимости от наличия или отсутсвия
e = null
мы любуемся одной из двух надписей:
1.
create 1
create 2

2.
create 1
create 2
Exception in thread «main» java.lang.OutOfMemoryError: Java heap space
Про DoEscapeAnalysis в курсе (правда пришлось освежить память что же это все таки такое). Параметр довольно интересный, но не встречал что бы его кто-то использовал кроме как для синтетических тестов — «посмотрите как оно круто работает». Может потому, что ситуации в реальной системе чуточку сложнее нежели «сейчас я тут насоздаю 100500 объектов в этом методе».

Кстати, если бы этот разговор состоялся бы около двух месяцев назад, я бы смог предоставить достаточно неплохую статистику как он может помочь\навредить в реальном приложении которое использует много-много памяти и живет месяцами.
а можно поподробней когда он может оказаться вреден?

по поводу только синтетики, то этот флаг уже очень давно стоит по умолчанию, и чтобы показать как jvm не может его приходится ручками отключать.
Поподробней когда он может оказаться вреден ничего сказать не могу — флаг прошел мимо меня. Но судя по поведению, у нас он был выключен — мелкие локальные объекты очень хорошо так плодились до подчистки их парньюшечкой судя по профайлеру
Ситуация возможна если у вас этот объект занимает БОЛЬШЕ ПОЛОВИНЫ памяти, так как действия для первого и второго случая:

первый:
1. выделили память под первый объект
2. выделяем память под второй
3. памяти нету, запускаем gc
4. так как объект1 еще доступен, то сколько бы не освободили мелочи на второй объект памяти не хватает
5. OOM

второй:
1. выделили память под первый объект
2. занулили указатель на переменную, первый объект уже недоступен и при необходимости, может быть очищен
3. выделяем память под второй
5. памяти нету, запускаем gc
6. так как объект1 уже недоступен, то очищаем его, памяти на второй объект хватает
7. вселенское счастье

НО если оказывается что у этого объекта переопределен finalize, то зануляй не зануляй, но OOM мы получим, так как все объекты с finalize собираются в 2 прохода:
1й проход — вызываем метод finalize
2й проход — действительной удаляем уже элемент из кучи

Так что увлекаться финализацией тоже не рекомендуется по пустякам, только когда это действительно необходимо, иначе можно поймать OOM почти на ровном месте.

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

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

Автору:
Дополнительно полезно почитать более свежий материал.

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

P.S. статья достаточно полезная, хотя по некоторым пунктам очень спорная.
Спасибо за статью по оптимизационным трюкам, очень хороший материал.
По поводу поведения в обоих случаях — важные дополнения. Только не понял насчёт объектов с переопределённым finalize() — чем он помешает сборке объекта?
Тем то, что сборка таких объектов производится в 2 этапа. На первой сборке выполняется finalize() и ставиться флаг, что объект финализирован, на второй — удаляется сам объект без выполнения finalize() если после его выполнения объект не стал достижим.
ничем не помешает, вот только память вернется в хип только после второго gc:

1. создали объект с finalize
2. закончилась память, дернули gc
3. gc дернул метод finalize у нашего объекта, удалил обычные объекты
4. продолжаем работать, опять закончилась память, дернули gc
5. gc видит наш объект и finalize уже отработан, удаляет объект и возвращает память

таким образом для сборки объекта с finalize должно пройти 2 gc вместо одного.

В случае с примером выше: имея объект с finalize мы дергаем gc один раз и достаточно памяти не получаем, так как объект еще не удалился, получаем oom.

Но изменяя пример на:
e = null;
System.gc();
System.gc();


Мы гарантировано собираем даже такие объекты и пример отрабатывает без oom, вот только в продакшен коде за дерганье gc надо руки отрывать, так что работать с finalize нужно очень осторожно.
UFO just landed and posted this here
Имею ввиду использование потоками ThreadLocal-переменных, описанных в классах. Каждый поток будет иметь свою независимо инициализированную копию данной переменной (соответственно, и уникальную ссылку на неё), что в случае с пулами потоков и невнимательном обращении с хранимыми в ThreadLocal-данными приведёт нас к указанной проблеме.
Не совсем понял к каким именно утечкам могут привести операции над строками? К замедлению работы программы они точно привести могут, но к утечкам?
У автора очень своеобразное понимание термина «утечка памяти», не обращайте внимание.
Вернёмся к приведённому в посте примеру с системой обработки сообщений.
Допустим, мы ведём обработку поступающих к нам сообщений, созданных по специально выбранному протоколу. Для простоты представим, что мы выполняем операцию получения префикса строк для данных сообщений и кэшируем эти значения.
Что произойдет при кэшировании десятка тысяч таких коротких префиксов при получении их через substring()? В памяти будут храниться все данные, содержащиеся во всех исходных сообщениях. И если их длина значительно превосходит суммурную длину всех префиксов в кэше, то именно на эту величину у нас и создастся утечка памяти.
Пример абстрактный, но точно отражает проблему. Мы используем нужные нам данные, заставляя перманентно храниться в памяти избыточные данные большего размера.
Во-первых, операция substring() никакой новой памяти не выделяет — кроме памяти самого экземпляр объекта java.lang.String. Благодаря иммутабельности строк, новая, полученная в результате операции substring(), строка наследует символьный буфер родительской строки.

Во-вторых, даже если ввести модифицирующую операцию (например toLowerCase) никакого «кэширования» не происходит. Экземпляры строк создаются и затем уничтожаются также как и обычные экземляры других классов. Единственное «кэширование» которое приходит на ум в данном случае — это принудительный вызов метода java.lang.String#intern() — но зачем?
Совершенно верно, я это все ниже расписал :) И даже в своем блоге отдельный пост на эту тему написал — потому что на самом деле, многие на этом могут спотыкаться.
Да, новой памяти не выделяет, но и старую не отдаёт.
String автор = ВойнаИМир.substring(12).
Всё как вы сказали, но вместо пары байт на автора в памяти будет вся война и мир и GC её не соберёт. Со стороны будет выглядеть как утечка…
substring(0,12) конечно же, но Вы поняли, для примера же…
Да, вы правы — такая проблема существует, однако мы все равно имеем фиксированный расход по памяти строго кратный O(n), что не совсем вяжется с моими понятием определения «утечка».
Замедление программы вызывает частые GC, которые происходят из-за недостатка памяти, в следствие постоянно торчащих там объектов, которые GC не может грохнуть
Интересная статья, хотя все вещи довольно очевидны.
Про «правильность» зануления ссылок с Вами, боюсь, не согласен Джошуа Блох=) Да и я тоже. Некрасиво это смотрится, к тому же
Спасибо за статью. Маленький комментарий по поводу workaround для substring. Intern возможно и решит проблему утечки памяти heap, но повальное его использование не по назначению, а ваш пример как раз сюда попадает, может вызвать утечку памяти в PermGen который обычно значительно меньше и вообще не всегда собирается GC.
Очень важное замечание, спасибо.
«A nasty variant of this blooper is when the substring is later interned by calling String.intern(). On some VMs, this means the large char[] object is now held onto forever by the VM's intern pool. Kiss that memory good-bye, because there's no way to free it again.» © wiki.eclipse.org/Performance_Bloopers
У меня очень маленький опыт использования Java, пару раз писал маленькие приложения. Помню намучился я с SAX анализатором. В разных версиях jre(1.7, 1.6) код работал по разному. При этом в 1.7 как раз и была проблема с превышением допустимого значения heap.
Вообще говоря, как раз в SAX при корректном использовании не должно быть проблем с этим, это в DOM-парсерах могут быть проблемы. SAX же не строит в памяти дерево документов, он проходит по нему использую более-менее константный объем памяти.
абсолютно верно, именно по этому и был выбран подход с SAX анализатором, так как нужно было читать большой файл построчно. Но на практике возникла проблема.
String prefix = longString.substring(0,5).intern();
И это в статье об утечках…
У автора какое-то странное понятие об утечках памяти. На самом деле что бы память в яве действительно потекла, надо, я даже не знаю что сделать*. То, что в мире явы зовут утечками, это просто неверная бизнес логика и банальное отсутствие знаний о том как все таки устроена память в яве и как работает сборщик мусора.

Некоторые вон, передавая в метод класс в конце метода приравнивают ссылку на него в null при этом оставляя комментарий в вида //объект больше не нужен, очистим память

* на классическую утечку похожа следующая ситуация:

static class Foo {
    static int sequence;
    int id = ++sequence;

    @Override
    public int hashCode() {
//        return id;
        return ++sequence;
    }
}

public static void main(String[] args) throws Exception {
    HashSet<Foo> foos = new HashSet<Foo>();
        Foo foo = new Foo();
    foos.add(foo);

    System.out.println(foos.remove(foo));
    System.out.println(foos.size());
}


Запустив эти несколько строчек мы увидим что-то вроде:

false
1


Тут мы получим что-то похоже на «утечку» потому как уже добраться из кода до элемента в сете будет достаточно проблематично (не берем в расчет полную итерацию), но даже в такой тяжелой ситуации у нас всегда есть выход — вызов метода clear()
Насчет substing, смотрите код этого метода в JDK. Итак.

public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}


Смотрим теперь по коду конструктор с такой же сигнатурой, видим:

// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}


Вывод — при вызове метода Substring, новый объект String конечно создается, но массив символов, используемый для хранения данных, не копируется. В новом объекте String будут просто использованы другие значения для offset/lenght, и ссылка на тот же самый массив символов относительно которых эти offset/length и берутся.
в этом то и проблема:
1. на вход подается строка из 10000 символов
2. вы получаете префикс с 2го по 5й символ
3. создается новая строка с проставленными offset, count, value
извиняюсь, отправилось рано, продолжим:

4. в value попадают все 10000 символов
5. сам префикс сохраняем в какой коллекции
5. повторяем итерациюю 1000 раз

Ожидаемый размер занятой памяти примерно = 4(символа префикса) * 1000 (итераций)
на практике = 10000 (размер первоначальной строки) * 1000 (итераций)

В то время как делая new String(str.substring(1,4)) мы получаем новую строку на 4 символа, а старая на 10000 символов свободно может уйти под gc.
Хм?

Ожидаемый объем памяти — 4(символа префикса) * 1000 (итераций). На практике — 1000 (потому что массив value- один), + 1000 * (разные offset/prefix), ну и плюс, если уж смотреть в глубь, 8 (по моему) байт на object descriptor) и выравнивание интов по 4х или 8-байтному смещению при создании новых объектов String.
Т.е. я согласен с тем, что если вы один раз вызвали substring чтобы вырезать маленькую подстроку, то он у вас создает доп. ссылку на цельный оригинальный массив, который будет из-за этого держаться в памяти. Но когда вы потом будете 1000 раз вызывать substring на таких строках, массив value[] не будет дублироваться, он будет один, со ссылками на него из 1000 разных мелких объектов.

Я не так понял вашу мысль, может?
Вероятно автор подразумевает что substring будет вызываться все время на разных строках, а не на одной и той же. Иначе смысл действительно теряется…
Немного не так: на вход у вас каждый раз новые строки, соответсвенно ради хранения нескольких символов вы будете держать в памяти весь массив на 10000 элементов.

Если у вас одна большая строка и множество вызовов substring от нее, тогда да, получаем даже выигрыш по памяти, так как value[] не дублируется.
Да, именно. В приведённом мной примере я подразумевал, что мы обрабатываем множество различных сообщений, применяя к их строковому представлению метод substring(), допустим, для получения префикса.
Для одного char-массива постоянные вызовы substring() — это наоборот хорошо.
Для каждого случая — свой подход. Однако, как заметили выше, не каждый разработчик знает про эту особенность java.lang.String. Поэтому я не мог не упомянуть этот случай. Поскольку в общем виде мы приходим к тому, что «привязываемся» к некому набору данных, используя лишь его ограниченную часть.
При единичных вызовах substring() к строкам большой длины стоит, конечно же, использовать конструктор.
вы же как раз приводите пример подтверждающий слова автора
this.value = value; — вот именно в этом месте и «утечка памяти»
если оригинальная строка удаляется то это value остаётся в памяти а оно никому в целом виде не надобно
Я бы посоветовал вообще вот такой вариант, с явным указанием области видимости. Тут как минимум всегда видно, что именно за объект лежит в e.
{
Elem e = new HeavyElem();
}
{
Elem e = new HeavyElem();
}

Впрочем подобный код все равно намекает, что скорее всего тут необходим рефакторинг, скажем экстракт метода.
блин
ну причем тут область видимости?
речь же совсем про другое
прочитайте тот параграф еще раз потом каменты которые уже были про тот параграф
При том, что присваивание переменной null ничего явно не укажет сборщику мусора. А вот отсутствие ссылок — укажет, что объект ушел из области видимости. Попробуйте донести свою мысль еще раз. Я всего лишь хотел донести простую мысль, что переприсваивание локальной переменной значения это плохой стиль, всегда есть более прямые способы донести свою мысль до кода.
там речь не шла про код который идёт подряд
речь шла про ситуацию когда надо большущий объект создать
а потом когда нибудь его понадобится пересоздать
да даже и не обязательно его

как вы собираетесь с помощью области видимости удалять ссылки на объект который полдня живёт, потому умирает в другом потоке и потом еще в другом пересоздается?
class Leak {
    static Leak instance;

    Leak() {
        instance = this;
        throw new Error();
    }
}
Этот пример не совсем про утечку, instance все таки будет иметь ссылку на созданный объект, с ним даже можно будет работать (правда насколько плодотворно — тот еще вопрос).

Если изменить в вашем примере конструктор на инициализатор:

class Leak {
static Leak instance;

{
instance = this;
throw new Error();
}
}


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

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

Sign up to leave a comment.

Articles