Co to jest JOL?
JOL (Java Object Layout) to mały zestaw narzędzi do analizy obiektów w JVM. Narzędzia te wykorzystuje Unsafe, JVMTI i Serviceability Agent (SA) do dekodowania rzeczywistego układu obiektów i referencji. Dzięki temu JOL jest znacznie dokładniejszy niż inne narzędzia bazujące na zrzutach sterty, założeniach specyfikacji itp.
Jak się do niego dobrać?
Dodajemy sobie zależność do maven’a:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

Aby dobrać się do wirtualnej maszyny mamy do dyspozycji taką klasę:

org.openjdk.jol.vm.VM

Która ma jedną statyczną metodę publiczną:

VirtualMachine.current()

Zwraca obiekt typu:

org.openjdk.jol.vm.VirtualMachine

Dzięki tej klasie mamy możliwość dobrać się do danych JVM np:
Odpalenie czegoś takiego:

VM.current().details();

Daje nam informacje o maszynie wirtualnej, np:

# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

Jak chcemy sprawdzić wielość nagłówka obiektu w pamięci wystarczy wpisać:

VM.current().objectHeaderSize()

Sprawdzenie czy dana klasa jest adnotowana przez adnotacje @Contended:

ContendedSupport.isContended(StandardObject.class)

W pakiecie JOL mamy również do dyspozycji kilka klas utils:

org.openjdk.jol.util.ClassUtils

umożliwiający dynamiczne ładowanie klas, również w systemowych ClassLoaderze

org.openjdk.jol.util.IOUtils

util pozwalający bezpiecznie zamykać strumienie

org.openjdk.jol.util.MathUtil

kilka matematycznych operacji

org.openjdk.jol.util.Multiset

Klasa opakowująca java.util.HashMap służąca jako inkrementer

org.openjdk.jol.util.ObjectUtils

w klasie są dwie metody, value i safeToString. Metoda value wyciąga wartość dla wybranego pola z klasy na trzy różne sposoby, metoda safeToString wykonuje toString() na typach array i primitive
Za pomocą dostępu do VM możemy wyciągać dużo szybciej dane z obiektów za pomocą “refleksji” – a tak na prawdę bezpośrednio odwołując się do pamięci.
Przykładowy benchmark:

Setup dla benchmarka:

private StandardObject object;
private Field field;
private VirtualMachine vm;
private long offset;

@Setup
public void setup () {
    object = new StandardObject();
    object.setVal(100.0);
    vm = VM.current();
    try {
        field = StandardObject.class.getDeclaredField("val");
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    }
    offset = vm.fieldOffset(field);
}

Standardowe wyciąganie danych przez refleksje:

@Benchmark
public void reflect () {
    try {
        field.setAccessible(true);
        blackhole.consume(field.get(object));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Wyciąganie danych bezpośrednio z pamięci

@Benchmark
public void reflectVM () {
    try {
        blackhole.consume(vm.getDouble(object, offset));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Wynik benchmarku:

Benchmark                Mode  Cnt       Score      Error   Units
BenchmarkApp.reflect    thrpt    5  159966.722 ± 4850.180  ops/ms
BenchmarkApp.reflectVM  thrpt    5  259729.201 ± 6932.689  ops/ms

Wynik pokazuje, że możemy spokojnie zastosować JOL do wyciągania danych za pomocą refleksji. Oczywiście może to być istotne w sytuacji gdy wykonuje po kilka set a nawet tysięcy takich operacji na sekundę.
Analiza obiektów za pomocą JOL:
Sprawdzanie adresu obiektu:

VM.current().addressOf(obiekt);

Sprawdzanie wielkości obiektu, w przypadku obiektów złożonych nagłówka:

VM.current().sizeOf(obiekt);

Wyświetlanie danych o obiekcie:

GraphLayout.parseInstance(obiekt).toPrintable();

Wynik:

net.szwadron.StandardObject@47d384eed object externals:
          ADDRESS       SIZE TYPE                        PATH                           VALUE
        782a82d58         24 java.lang.String            .string                        (object)
        782a82d70         96 [C                          .string.value                  [f, s, d, f, d, s, f, d, s, f, d, s, s, d, d, s, d, s, c, d, v, d, s, v, 2, 3, r, 2, 3, e, 2, 3, e, d, w, d, 2, 3]
        782a82dd0         24 java.lang.String            .standardObject.string         (object)
        782a82de8        112 [C                          .standardObject.string.value   [f, s, d, f, d, s, f, d, s, f, d, s, s, d, d, s, d, s, c, d, v, d, s, v, 2, 3, r, 2, 3, e, 2, 3, e, d, w, d, 2, 3, e, w, r, e, w, r, w, e]
        782a82e58         24 (something else)            (somewhere else)               (something else)
        782a82e70         24 net.szwadron.StandardObject                                (object)
        782a82e88        384 (something else)            (somewhere else)               (something else)
        782a83008         24 java.lang.Double            .val                           10.0
        782a83020         24 net.szwadron.StandardObject .standardObject                (object)

Wyświeltenie tylko i wyłacznie wilkości:

GraphLayout.parseInstance(obiekt).toFootprint();

Wynik:

net.szwadron.StandardObject@47d384eed footprint:
     COUNT       AVG       SUM   DESCRIPTION
         2       104       208   [C
         1        24        24   java.lang.Double
         2        24        48   java.lang.String
         2        24        48   net.szwadron.StandardObject
         7                 328   (total)

Sprawdzenie całkowitej ilości obiektów:

GraphLayout.parseInstance(obiekt).totalCount();

No i na koniec sprawdzenie tylko wielkości obiektów:

GraphLayout.parseInstance(obiekt).totalSize();

Zwraca nam sumę wszystkich obiektów. Oczywiście wszystkie te dane mamy również wyżej, ale jeśli chcemy mieć jakieś metryki z wielkości danych jakie przetwarzamy lub wiedzieć ile pamięci potrzebujemy to ostatnia metoda w pełni nam to ogarnie.
Na koniec tylko wspomnę, że w JOL również mamy coś takiego jak HeapDumpReader który pozwala nam załadować do pamięci dumpa pamięci JVM i go analizować, czyli jakby ktoś chciał sobie napisać własnego analizera do pamięci.
Źródło:
http://openjdk.java.net/projects/code-tools/jol/

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *