System.gc () против кнопки GC в JVisualVM / JConsole

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

Чтобы получить порядок величины для прогона с реальным пиком в 100 МБ (я тестировал несколько раз точно такую же конфигурацию ввода / параметров, заставляя память jvm с -Xmx и -Xms уменьшать значение, я получаюИсключение в теме "главный" java.lang.OutOfMemoryError: Превышен лимит накладных расходов GC < 100 МБ, со стабильными и воспроизводимыми результатами), он занимает около 1,1 ГБ, чтоВот почему для меня крайне важно получить реальные цифры, потому что они сильно отличаются!

я провел последние 10 дней, читая вопросы в Интернете и в stackoverflow, что я на самом деле знаю:

System.gc () "предложить" GC запускается, не вынуждает его каким-либо образом, поэтому на него нельзя положиться для обнаружения пиков использования памяти

Обычно предлагается подсчитать занятость объекта (я виделРазмер проект для этого, я попробовал и работает отлично, даже если он не соответствует моим потребностям), это не представляется возможным для меня, потому что интенсивное распределение памяти происходит из-за создания большого количества итераторов коллекции (набор, список и карта) в разных методы, вызываемые очень большое количество раз (скажем, по миллионам каждый за 10 минут, что я помню), поэтому было бы крайне сложно обнаружить все вовлеченные объекты и выполнить суммы (я отлаживал много раз за несколько дней с графики потребления памяти без возможности идентифицировать только одну бутылочную горловину)

Нет никакого способа легко получить занятость памяти метода (выраженную как пик выделения памяти объекта)

Дело в том, что я сам испытал, что вызовы System.gc () ненадежны (например, разные прогоны одной и той же конфигурации, разное чтение памяти после System.gc () из-за того, что GC действительно вызывается или нет), но когда Я нажимаюКнопка GC " в JVisualVM или Jconsole этоникогда не запускает GC или отказывается это делать.

Итак, мой вопрос: вызывая их реализацию этой кнопки (я непока не попробую, но для чего яВы читали до сих пор, кажется возможным с помощью jconsole.jar сприложить API) будет отличаться от вызова System.gc () непосредственно из моего кода, что решит мою проблему? Если нет, то как вы объяснитедетерминированное поведение " этой кнопки?

До сих пор я проводил ручное тестирование реального пика памяти при 10 увеличивающихся размерах схем (для этого вида измерений схемы генерируются автоматически из одного "параметр сложности ") и я построил ожидаемую кривую, если я не смогу найти лучшего решения, я хочу запустить свой код в качестве внешнего jar с -Xmx / -Xms, равным немного меньшему, чем мой прогноз ожидаемого пика памяти, ловя OutMemoryException во внешнем процессе ErrorStream и перезапуск с увеличенным объемом памяти до полного выполнения. (Если наивный прогноз памяти не будет достаточно надежным, я буду применять соответствующие методы машинного обучения). Я знаю, что это не элегантное решение, но в моем сценарии (академическом) я могу позволить себе потратить дополнительное время на эти измерения. Если у вас есть другие предложения или улучшения к этому методу грубой силы, вы (крайне) можете поделиться ими.

Информация о системе (машина Fedora 17, 64 бит):

Java-версия "1.7.0_04" Java (TM) SE Runtime Environment (сборка 1.7.0_04-b20) Java HotSpot (TM) 64-битная виртуальная машина сервера (сборка 23.0-b21, смешанный режим)

Заранее спасибо, Алессандро

 Marko Topolnik09 нояб. 2012 г., 11:06
Вы пытались проверить просто с двумя или тремяSystem.gc() звонки подряд, с возможнымsleep между ними? Потому что я еще не видел, чтобы этот метод потерпел неудачу. Первый вызов может вызвать незначительную коллекцию, но второй уже наверняка вызовет полный сборщик мусора.
 Alessandro S.09 нояб. 2012 г., 14:32
@MarkoTopolnik: в последние часы я реализовал свой "GetMemory» Метод, использующий ваше предложение, после нескольких попыток кажется достаточно стабильным с 2 вызовами и только 1сек сна. Его стабильность позволила мне найти действительно хорошую точку для оценки пиков памяти, поэтому моя проблема решена (используя 2 разных профиля для памяти / времени). Если вы положите это в ответ, я приму это! Еще раз большое спасибо.
 Alessandro S.09 нояб. 2012 г., 11:10
Нет @MarkoTopolnik, я пробовал только один вызов System.gc (), я попробую, если он докажет "вполне» стабильный может быть достаточно для построения чего-то. Спасибо!

Ответы на вопрос(4)

Решение Вопроса

У меня есть довольно положительный опыт с этим тривиальным подходом:

System.gc();
Thread.sleep(500);
System.gc();

Одного прогона GC часто недостаточно из-за проблем с завершением объекта, когда объект может быть воскрешен в процессе завершения. Поэтому дополнительная память освобождается во втором прогоне ГХ.

Заметьте, это, как и другие, казалось бы, "умнее», подходы, все эвристические и весьма зависят от точной версии JVM, включая его конфигурацию GC. Но во многих случаях вас не так сильно интересует общность: если она работает прямо сейчас и позволяет вам проводить измерения, это путь.

1) System.gc () "предложить" GC запускается, не вынуждает его каким-либо образом, поэтому на него нельзя положиться для обнаружения пиков использования памяти

Это то, что написано в спецификации, но если вы используете OpenJDK или HotSpot, он всегда будет выполнять Full GC, если вы не отключите его.

Обычно предлагается подсчитать занятость объекта

Я бы предложил использовать коммерческий профилировщик памяти. Я бы запустил JVM с максимальным объемом 8 ГБ и посмотрел, сколько он пытается использовать. После этого я бы увеличил или уменьшил его на основании вашего суждения о том, хочет ли он большего или нет.Кажется, он его использует.

Нет никакого способа легко получить занятость памяти метода (выраженную как пик выделения памяти объекта)

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

Если нет, то как вы объяснитедетерминированное поведение " этой кнопки?

Я бы отнес это к субъективному анализу. ;)

В идеале вы должны использовать JVM с 2-3-кратным минимальным объемом памяти, необходимым для эффективной работы. Попытка сэкономить несколько 100 МБ, которые стоят менее 1 доллара, не всегда полезна. ;)

 Marko Topolnik09 нояб. 2012 г., 11:29
Но это описывает только одну ситуацию. Что если я хочу запустить 100 экземпляров одного и того же приложения на одной физической машине? Теперь коэффициент 10х больше не умножает 1 доллар.
 Marko Topolnik09 нояб. 2012 г., 11:17
Да, этов соответствии с моими выводами, где я бегу два или даже три GC 'с льготным периодом между ними.
 Peter Lawrey09 нояб. 2012 г., 11:40
@MarkoTopolnik Вы можете легко запустить 10 ГБ на ПК (фактически на ноутбуке). Любая организация, которая считает, что 10 ГБ на многих машинах выиграет, выиграет.Я не должен платить минимальную заработную плату одному человеку, чтобы масштаб проблемы мог измениться, но отношение стоимости оборудования к стоимости вашего времени все еще остается проблемой.
 Marko Topolnik09 нояб. 2012 г., 11:14
Несмотря на то, что первым может быть полный сборщик мусора, все еще есть проблемы с финализацией и т. Д., Когда многим объектам на самом деле требуется два полных прохода сборщика мусора для сбора.
 Marko Topolnik09 нояб. 2012 г., 11:52
@PeterLawrey Я просто привел пример. Разница между 1 МБ и 100 МБ огромна, поскольку учитывается масштабный коэффициент. Они просто не находятся в одной лиге, и это имеет мало общего с буквальной ценой DRAM. В моей собственной компании мы имеем именно эту проблему: мы используем распределенную архитектуру со многими автономными процессами Java, связывающимися через JMS. Потеря оперативной памяти, возникающая из-за того, что каждый узел является полноценным экземпляром JVM, представляет собой определенную болевую точку и "черный глаз" на нашей архитектуре.
 Alessandro S.09 нояб. 2012 г., 11:17
Я добавил некоторую системную информацию к вопросу, спасибо @PeterLawrey. Под занятием метода я имею в виду кучу, используемую посредством выделения объектов, в данном случае всего этого итератора.
 Peter Lawrey09 нояб. 2012 г., 11:33
Вы хотите сэкономить 1 МБ на 100 экземпляров, может стоить всего 50 центов или около пяти минут, включая тестирование одного человека на минимальную заработную плату. YMMV. Если вы хотите сэкономить 100 МБ на 100 компьютерах, это может стоить $ 50, что может стоить потратить 7 минут, включая правильное тестирование на минимальную заработную плату.
 Peter Lawrey09 нояб. 2012 г., 11:15
Может быть больше, если они ссылаются друг на друга, или очередь завершения не имеетт сливают между ГК. ;)
 Peter Lawrey09 нояб. 2012 г., 11:38
Сколько стоит ваше свободное время и сколько клиент может представить, что его оборудование будет стоить для обновления, будет варьироваться. До тех пор, пока вы не профилируете память своего приложения, пик может быть не очень полезным. то есть может быть лучше потратить время на сокращение потребления. Что я сделал, так это перешел в кучу памяти с отображенными в память файлами. Я регулярно отображаю в 500 ГБ или более данных, но мой максимальный размер кучи составляет 1 ГБ, что нет использовал много.
 Marko Topolnik09 нояб. 2012 г., 11:38
@PeterLawrey Нет, я хочу сэкономить 99 МБ на 100 экземплярах, всего 10 ГБ. Это может означать разницу между незаметной работой на одном хосте и необходимостью работы всего кластера. Разница в количестве настроек, экспертизе, бизнес-решениях и т. Д. Огромна.
 Alessandro S.09 нояб. 2012 г., 11:34
Я в целом согласен с тобой @PeterLawrey, но мой проротип предназначен только для показа времени / памяти "Технико-экономическое» моего предложенного метода, поэтому для меня принципиально оценить реальный пик (наивное измерение для 100 МБ говорит о том, что больше 1 ГБ, я действительно не могу показать это число, учитывая, что это не реальный пик). Другая проблема заключается в том, что при увеличении размера схемы мне нужно получить более высокий пик, если GC делает то, что он хочет, чтобы яЯ не гарантирован, чтобы получить его, и если на самом деле это не происходит сейчас, поэтому графики для меня не годятся. Кстати, моя задача - не оптимизировать, а только оценить память.
 Marko Topolnik09 нояб. 2012 г., 11:19
@PeterLawrey У меня есть проблемы с вашим аргументом, основанным на цене за ГБ, потому что это определенно вредит масштабируемости, поэтому ограничивает варианты использования. Процесс, выполняющийся в 1 МБ, не относится к той категории, которой требуется 100 МБ, независимо от того, сколько стоит этот объем ОЗУ.
 Peter Lawrey09 нояб. 2012 г., 11:27
@MarkoTopolnik Имеет ли смысл тратить $ 1 вашего времени, чтобы сэкономить $ 10 памяти при повторном использовании? Имеет ли смысл тратить 100 долларов вашего времени (затраты для компании), чтобы сэкономить 10 долларов памяти (затраты для компании)? Если вы неЕсли вы спросите себя, вы получите примеры людей, которые тратят 1000 долларов (включая исправление возникающих ошибок), чтобы сэкономить менее 0,005 долларов памяти (например, 1 МБ;). Для меня это безумие.

Вы можете заставить GC, как это ....

private static void force_gc()
{
    Object obj = new Object();
    WeakReference<object> ref = new WeakReference<object>(obj);
    obj = null;
    while (ref.get() != null)
    {
        Log.d(LOGTAG, "Forcing gc() ...");
        System.gc();
    }
}
<p>кроме этого ... яМне интересно посмотреть, где этот вопрос идет.</p></object></object>
 Marko Topolnik09 нояб. 2012 г., 14:43
Обратите внимание, что это не я, кто положил отрицательный ответ на ваш ответ. Я просто прокомментировал свой опыт.
 Marko Topolnik09 нояб. 2012 г., 14:46
Способ, которым он на самом деле работает хуже, чем простой метод грубой силы, заключается в том, что ссылка почти наверняка будет очищена при первом запуске, и очень часто требуется второй запуск, чтобы отсеять весь мусор.
 Marko Topolnik09 нояб. 2012 г., 13:03
Я играл с этим раньше, это ненадежно. Слабый реф очищается нене гарантирую вещь. В частности, это неt гарантировать, что референт был собран.
 Shark09 нояб. 2012 г., 14:31
@MarkoTopolnik так для (int i = 0; i <3; i ++) {System.gc (); сна (100); } лучше?
 Marko Topolnik09 нояб. 2012 г., 14:37
Вы'Я был бы удивлен, узнав, что в некоторых обстоятельствах это действительно может быть лучше, но это, безусловно, проще, при достижении точно таких же гарантий. Код со слабыми ссылками выглядит просто такделает что-то умное, так чтовводит в заблуждение.
 Shark09 нояб. 2012 г., 14:44
@MarkoTopolnik "Код со слабыми ссылками выглядит просто такделает что-то умное, так чтовводит в заблуждение ". не мог»не сказал это лучше - именно поэтому я думал, что это было лучше. Я не' заботиться о понижении, я знаю, ответ неЭто не имеет отношения к задаваемому вопросу.

Насколько я знаю, Jconsole или любой другой инструмент использует только System.gc (). Там нет другого варианта. Как все знают, Java говорит всем не полагаться на System.gc (), но это незначит, это не такне работает вообще.

Итак, что касается вашего запроса, вы, похоже, обеспокоены тем, что при нажатии этой кнопки вызывается GC напрямую и до сих пор Java говорит только System.gc "предполагает» позвонить в GC. Я говорю, эта кнопка также вызывает System.gc () & это толькопредполагает» Java, чтобы попробовать для GC, и случается так, что java решает выполнить GC в это время сам (это не гарантировано, но каким-то образом это делает java).

Итак, чтобы доказать этот факт, я просто создал простую программу, которая просто создает множество объектов. Он прокомментировал строку с "System.gc ()», Теперь попробуйте сначала запустить эту же программу с комментарием System.gc () & затем, раскомментировав System.gc (). Убедитесь, что аргументы VM указаны как -verbose: gc -XX: + PrintGCTimeStamps -XX: + PrintGCDetails.

package ravi.tutorial.java.gc;

/**
 * Just to test GC. RUn with below VM arguments.
 * 
 * -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails
 * 
 * 
 * @author ravi.k
 * 
 */
public class TestGC {

    public static A a;

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 100; i++) {
            populateObjects();
            System.out.println("population done for batch: " + i);
        }

    }

    public static void populateObjects() {
        for (int i = 0; i < 100000; i++) {
            a = new A("A");
        }
        //System.gc();
    }

}

class A {
    String s;

    public A(String s) {
        this.s = s;
    }
}

Вот частичные выходы из моей машины.

Commened System.gc (): здесь GC вызывается по желанию jre.

population done for batch: 0
population done for batch: 1
population done for batch: 2
population done for batch: 3
population done for batch: 4
population done for batch: 5
population done for batch: 6
population done for batch: 7
population done for batch: 8
population done for batch: 9
0.332: [GC 0.332: [ParNew: 17024K->410K(19136K), 0.0024479 secs] 17024K->410K(83008K), 0.0025219 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
population done for batch: 10
population done for batch: 11
population done for batch: 12
population done for batch: 13
population done for batch: 14
population done for batch: 15
population done for batch: 16
population done for batch: 17
population done for batch: 18
population done for batch: 19
0.344: [GC 0.344: [ParNew: 17434K->592K(19136K), 0.0011238 secs] 17434K->592K(83008K), 0.0011645 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] 
population done for batch: 20
population done for batch: 21
population done for batch: 22
population done for batch: 23
population done for batch: 24
population done for batch: 25
population done for batch: 26
population done for batch: 27
population done for batch: 28
population done for batch: 29
population done for batch: 30
0.353: [GC 0.353: [ParNew: 17616K->543K(19136K), 0.0011398 secs] 17616K->543K(83008K), 0.0011770 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
population done for batch: 31
population done for batch: 32
population done for batch: 33

Uncommented System.gc (): здесь GC вызывается для каждого пакета. Теперь System.gc () предлагает только GC, но java выбирает сам запуск GC в это время. Это точно такой же случай для этой волшебной кнопки GC в других инструментах :)

0.337: [Full GC (System) 0.337: [CMS: 0K->400K(63872K), 0.0219250 secs] 3296K->400K(83008K), [CMS Perm : 4423K->4422K(21248K)], 0.0220152 secs] [Times: user=0.04 sys=0.00, real=0.02 secs] 
population done for batch: 0
0.364: [Full GC (System) 0.364: [CMS: 400K->394K(63872K), 0.0161792 secs] 2492K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0162336 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
population done for batch: 1
0.382: [Full GC (System) 0.382: [CMS: 394K->394K(63872K), 0.0160193 secs] 2096K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0160834 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
population done for batch: 2
0.399: [Full GC (System) 0.399: [CMS: 394K->394K(63872K), 0.0160866 secs] 2096K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0161489 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
population done for batch: 3
0.417: [Full GC (System) 0.417: [CMS: 394K->394K(63872K), 0.0156326 secs] 2096K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0156924 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
population done for batch: 4
0.434: [Full GC (System) 0.434: [CMS: 394K->394K(63872K), 0.0157274 secs] 2096K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0157897 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
population done for batch: 5

Чтобы добавить больше, это так же, как темы. Нет никакой гарантии, когда поток запускается, но всякий раз, когда мы пишем какую-либо примерную программу потока, поток запускает это время сам. Таким образом, мы не должны обвинять Java, почему он запустился, как только начался поток :). Ява только говорит не полагаться на эти вещи, но они действительно работают. Кроме того, хотя они работают в некоторых случаях неЯ имею в виду, они будут работать каждый раз. Даже те инструменты jconsole могут не выполнять GC, просто мы этого никогда не видели.

 Alessandro S.09 нояб. 2012 г., 14:26
Спасибо, хорошо, я знаю, что всегда вызывается System.gc (), поэтому не стоит тратить время на вызов jconsole из моей программы, вы сэкономили много моего времени!

Ваш ответ на вопрос