На прошлой неделе я написал статью об использовании jhat для получения статического снимка объектов Ruby в куче JRuby . Следующий шаг — попытаться получить похожую информацию из запущенного приложения с периодическими обновлениями. Другими словами, загляните внутрь кода JRuby во время его выполнения на JVM и выясните количество объектов Ruby, которые были распределены с разбивкой по классу Ruby для каждого объекта.
Инструмент, который я выбрал на этот раз, был BTrace . «B» обозначает «Байт-код», а BTrace предоставляет возможность профилирования «накатить свой». Другими словами, вы получаете полный контроль над тем, что инструментируется в вашем Java-приложении. Компромисс, по сравнению с другими инструментами профилирования, заключается в том, что вы должны выполнять больше работы. В частности, вы должны написать сценарии, которые контролируют инструментарий. К счастью, язык сценариев знаком: Java вместе с некоторыми аннотациями, которые предоставляет BTrace.
BTrace — простая утилита командной строки, для которой просто необходим идентификатор процесса JVM для инструмента и имя файла сценария (вы можете предоставить исходный файл .java или использовать btracec для создания файла .class). Чтобы поэкспериментировать с этим, мне нужна была программа на Ruby, которая создавала бы множество объектов. Я написал это простое приложение:
class Every end class EveryOther end puts "Hello World" i = 0; all = Array.new some = Array.new while (true) some[i] = EveryOther.new if (i%2==0) all[i] = Every.new puts i if (i%1000000==0) i+=1 end
У него просто есть бесконечный цикл, который распределяет объекты. Я использую JRuby 1.1 для его запуска и передаю -J-Xmx2048m в командной строке JRuby, чтобы программа могла довольно долго запускаться, прежде чем использовать все пространство кучи и выйти из нее с помощью OutOfMemoryError .
Мой скрипт BTrace:
package org.jruby; import com.sun.btrace.annotations.*; import static com.sun.btrace.BTraceUtils.*; import com.sun.btrace.aggregation.Aggregation; import com.sun.btrace.aggregation.AggregationFunction; import com.sun.btrace.aggregation.AggregationKey; /** * Create a histogram of RubyObject instances, * keyed by RubyClass classId. * @author Gregg Sporar */ @BTrace public class RubyClassHistogram { private static Aggregation sum = newAggregation(AggregationFunction.SUM); /* * Instrument the public RubyObject contstructor */ @OnMethod( clazz="org.jruby.RubyObject", method="<init>", type="void (org.jruby.Ruby, org.jruby.RubyClass)" ) public static void onNewObject(org.jruby.RubyObject self, org.jruby.Ruby ruby, org.jruby.RubyClass rubyClass) { String classId = rubyClass.classId; AggregationKey key = newAggregationKey(classId); addToAggregation(sum, key, 1); } @OnTimer(4000) public static void print() { printAggregation("RubyObject Instances Created:", sum); } }
BTrace вызывает мой метод print () каждые 4 секунды для отображения текущих значений в гистограмме. Например:
Создано экземпляров RubyObject:
строка 18
EveryOther 2934364
Every 5868731
Пожалуйста, обратите внимание:
- Гистограмма показывает количество созданных объектов , а не количество объектов, которые в данный момент находятся в куче. В примере приложения-игрушки, которое я использовал, эти числа одинаковы, но в реальных приложениях число живых объектов обычно меньше общего числа созданных объектов. Я не очень хорошо разбираюсь во внутренностях JRuby, но я предполагаю, что вышеприведенный скрипт BTrace можно было бы усовершенствовать, чтобы он также использовал метод в JRuby, который удаляет объект Ruby из кучи JRuby.
- BTrace требует JDK 6 или выше.
- BTrace все еще находится в стадии разработки — используйте его на свой страх и риск. Из того, что я видел и слышал, он выглядит надежным и стабильным, но я не знаю, буду ли я использовать его в производственной системе. В частности, у меня пока нет достаточного опыта работы с ним, чтобы иметь представление о количестве накладных расходов, которые он будет вносить при использовании со скриптом, подобным показанному здесь.