Статьи

Использование VisualVM, BTrace и старого доброго веб-поиска


История начинается с того, что я выхожу из сетки каждый день.
Это действительно позволяет мне ценить хорошую батарею, и, хотя моя батарея Mac довольно приличная, я буквально
чувствую, как она разряжается при выполнении тяжелой работы на Java с
IDE NetBeans ,
Maven и большими перекомпиляциями.

Теперь, в то время как истощение батареи при большой нагрузке совершенно нормально, это действительно заставит вас держать острый глаз на использование процессора в OSX Activity Monitor. Вот как я заметил, что во время простоя IDE NetBeans занимал около 16% моего процессора … на самом деле ничего не делал.

Первое, что я хотел знать, было то, что там происходило? Поэтому я взял VisualVM , подключил его к экземпляру IDE NetBeans и вызвал дамп потока :

По сути, это список трасс стека для всех потоков, так что вы можете видеть текущее состояние выполнения приложения. В моем случае, я, вероятно, не нажал кнопку в нужное время, так как я перехватил все потоки в состоянии ОЖИДАНИЯ, что означает, что нужно было использовать не так много ЦП: 

Ну, так как дамп потока — это просто быстрый снимок, было ясно, что я не собираюсь получить хорошее представление о том, что потребляет процессор, поэтому я выбрал вариант с большим весом: профилирование процессора.

Начать это довольно легко со стороны VisualVM, но для того, чтобы начать сбор данных, требуется вечность. Причиной является инструментарий байт-кода, который выполняется профилировщиком для многих классов IDE NetBeans. Это означает, что если VisualVM жалуется, что приложение не отвечает, и предлагает прекратить профилирование, вам нужно ответить «Нет» и позволить ему выполнить свою задачу:

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

org.netbeans.core.TimableEventQueue.postEvent with 31.3%
apple.laf.CUIAquaProgressBar$Animator.actionPerformed with 25.1%
org.netbeans.core.TimableEventQueue.dispatchEvent with 5%

Итак, похоже, что EventQueue (ну, в любом случае, тот, который используется в IDE NetBeans) все это время был действительно занят, вызывая специфичный для OSX класс аниматора ProgressBar. Единственное было — на экране не было индикатора прогресса! На самом деле вся среда IDE NetBeans была скрыта … поэтому Swing должен был быть очень тихим.

Ну, это начало выглядеть так, как будто у нас была какая-то «утечка» здесь, то есть какой-то объект, который явно не удаляется должным образом. Я сразу подумал, что виновником может быть какой-то класс внутри API / интерфейса пользователя NetBeans Progress, поскольку этот модуль отображает этот небольшой компонент прогресса в правом нижнем углу главного окна. Хотя это было просто предчувствие, так как я немного знаю о внутренностях среды IDE NetBeans, но у меня еще не было возможности доказать это или, что еще более важно,чтобы исправить это .

Я вернулся в VisualVM, зная, какие классы искать, и запросил дамп кучи. Это снимок памяти приложений на тот момент. Я вошел во вкладку Classes и искал наш класс CUIAquaProgressBar $ Animator :

 

Теперь я решил найти ссылку на JProgressBar и, конечно же, используя область « Поля» на вкладке « Экземпляры », нашел ссылку на индикатор выполнения:

 

Итак, действительно, у нас есть некоторые дикие JProgressBars, вызываемые, более точно NbProgressBars . Сейчас мы куда-то добираемся!

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

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

Во-первых, я хотел увидеть звонки аниматора. Они были в методе actionPerformed , что привело к аннотации для метода btrace:

@OnMethod(clazz="apple.laf.CUIAquaProgressBar$Animator",
method="actionPerformed")

Мне также был нужен сам объект, поэтому я использовал аннотацию @Self, чтобы получить его из btrace:

public static void lnfaction(@Self Object animator)

Теперь, из приведенного выше анализа дампа кучи, я знал, что у меня есть ссылка на индикатор выполнения, поэтому я немного подумал, чтобы добраться до него:

println( strcat(strcat(str(timeNanos()), " : animator called "),str(animator)));
Field f =  field("apple.laf.CUIAquaProgressBar$Animator", "this$0");
/*apple.laf.CUIAquaProgressBar*/ Object pbar = get(f,animator);
println(pbar);

Field f2 = field("apple.laf.CUIAquaProgressBar", "progressBar");
/*org.netbeans.modules.progress.ui.NbProgressBar*/ Object swingbar = get(f2, pbar);
println(swingbar);

Обратите внимание, насколько вы ограничены при написании скрипта … на самом деле не разрешены вызовы методов, кроме вызовов из com.sun.btrace.BTraceUtils (которые статически импортированы). Даже конкатенация строк должна быть явной, используя BTraceUtils.strcat ! Тем не менее, это дает вам достаточно возможностей, чтобы прогрессировать довольно приятно.

Затем я скомпилировал скрипт, используя btracec Script.java , а затем выполнил его с помощью btrace $ PID Script.class, где $ PID — фактический числовой идентификатор процесса (например, 2377). Выполнение выводит много отладочной информации, и я обнаружил, что у меня есть несколько экземпляров, которые выглядят так:

org.netbeans.modules.progress.ui.NbProgressBar@48aa00ec

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

ПРИМЕЧАНИЕ . Я сделал этот шаг вручную, подготовив команды в терминале, но, вероятно, вы могли бы подключить агент btrace с самого начала к IDE и запустить этот скрипт. Это не сработало для меня, и я не стал проверять это.

Поиск трассировки стека для конструктора — это просто вопрос другого метода сценария и BTraceUtils.jstack () :

@OnMethod(clazz="org.netbeans.modules.progress.ui.NbProgressBar",
method="<init>")
public static void instantiate(@Self Object o ){
println(strcat("Instantiated org.netbeans.modules.progress.ui.NbProgressBar: ",str(o)));
jstack();
println("----");
}

И это где мы бьем золото! Две общие трассировки стека:

org.netbeans.modules.progress.ui.StatusLineComponent.createBar(StatusLineComponent.java:170)
org.netbeans.modules.progress.ui.StatusLineComponent.initiateComponent(StatusLineComponent.java:377)
org.netbeans.modules.progress.ui.StatusLineComponent.processSelectedProgressEvent(StatusLineComponent.java:329)
org.netbeans.progress.module.Controller.run(Controller.java:337)
java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:300)
java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:210)
java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:200)
java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:195)
java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:187)
java.awt.EventDispatchThread.run(EventDispatchThread.java:121)

и

org.netbeans.modules.progress.ui.ListComponent.<init>(ListComponent.java:105)
org.netbeans.modules.progress.ui.StatusLineComponent.createListItem(StatusLineComponent.java:439)
org.netbeans.modules.progress.ui.StatusLineComponent.processProgressEvent(StatusLineComponent.java:290)
org.netbeans.progress.module.Controller.run(Controller.java:339)
java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:300)
java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:210)
java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:200)
java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:195)
java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:187)
java.awt.EventDispatchThread.run(EventDispatchThread.java:121)

Другими словами, наши «дырявые» экземпляры здесь:

org.netbeans.modules.progress.ui.StatusLineComponent.createBar

org.netbeans.modules.progress.ui.ListComponent

Что я до сих пор не понял, так это то, что заставляет аниматора продолжать вызываться. Здесь я прибег к старому доброму поиску в Google и набрал « apple.laf.CUIAquaProgressBar $ Animator ». Это очень необычный запрос, так как я только что получил 7 предметов, а четвертый был отчет об ошибке IDEA . Они жаловались на одно и то же! И один из плакатов нашел обходной путь … просто удалите интерфейс.

progress.getUI (). uninstallUI (progress) кажется волшебной чертой.

С этими новыми знаниями я изучил два целевых класса, чтобы выяснить, где выпущен ProgressBar. Конечно же, один из них имеет DiscardBarметод, в то время как другой потребуется дополнительный метод. Я исправляю методы, изменяю сценарий btrace, чтобы дважды проверять эти методы, и перестраиваю IDE NetBeans.

ПРИМЕЧАНИЕ : это единственный недостаток до сих пор. В то время как исследование выполнялось непосредственно в двоичных файлах IDE, для его исправления требовался исходный код.

Вот и все. Запуск сценария btrace с пропатченными классами показал, что класс аниматора OSX больше не вызывается! Несмотря на то, что это не было ошибкой per se в IDE, больше похоже на проблему OSD JDK, обходной путь действительно уменьшил использование моего процессора!

После очистки патча я готов отправить его в NetBeans для включения. Удивительно быстро, мой отчет об ошибкахбыл принят и включен примерно за 1 час 30 минут. Я полагаю, что пометить его как приоритет P2 немного ускорило ситуацию.

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