#chiroito ’s blog

Java を中心とした趣味の技術について

JFR Under the hood : JFRイベントの書き込み -Java編-

結論

C/C++ 側で Java のjdk.jfr.internal.EventWriterが作成されます。このインスタンスにはThreadLocalに作られたJava用のバッファへのポインタを保持しています。イベントが書込まれるとEventWriterはそのイベントをこのバッファへ書込みます。この書き込み処理は各イベントに commit メソッドを使って行われます。このメソッドは親クラスであるEventクラスでは空実装ですが、jdk.jfr.FlightRecorderクラスがイベントを読み込む際にバイトコードを操作されて、commitメソッドはイベントが持っているフィールドを先ほどのバッファに書くように変更されます。

今回はこの様なイベントクラスを使って、その変化を確認していきます。

@Label("JfrEvent")
public class JfrEvent extends Event {

    @Label("id")
    private int id;

    @Label("name")
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

JFR では、先ほどのイベントクラスを C/C++ レベルで書き換えます。

  • @src/hotspot/share/jfr/instrumentation/jfrEventClassTransformer.cpp

またイベントに合わせたイベントハンドラはASM と言うツールを使ってバイトコードを動的に作成します。このイベントハンドラはjdk.jfr.internal.handlers.EventHandlerを継承しています。

  • @src/jdk.jfr/share/classes/jdk/jfr/internal/EventHandlerCreator.java
  • @src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriterMethod.java

イベントのクラスは以下の様に修正されます。

public class JfrEvent extends Event {
    private int id;
    private String name;
    private static EventHandler eventHandler;
    private transient long startTime;
    private transient long duration;

    public String getName() {
        return this.name;
    }

    public JfrEvent() {
    }

    static {
        FlightRecorder.register(JfrEvent.class);
    }

    public void begin() {
        this.startTime = EventHandler.timestamp();
    }

    public void end() {
        this.duration = EventHandler.duration(this.startTime);
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void commit() {
        if (this.isEnabled()) {
            if (this.startTime == 0L) {
                this.startTime = EventHandler.timestamp();
            } else if (this.duration == 0L) {
                this.duration = EventHandler.timestamp() - this.startTime;
            }

            if (this.shouldCommit()) {
                ((EventHandler1754_1595079330631_153352)eventHandler).write(this.startTime, this.duration, this.id, this.name);
            }

        }
    }

    public boolean isEnabled() {
        return eventHandler.isEnabled();
    }

    public boolean shouldCommit() {
        return eventHandler.shouldCommit(this.duration);
    }
}

また、このイベントクラス用のイベントハンドラは以下の様に作られます。

public final class EventHandler1754_1595079330631-153352 extends EventHandler {
    private final StringPool stringPool3 = this.createStringFieldWriter();

    private EventHandler1754_1595079330631_153352(boolean registered, EventType eventType, EventControl eventControl) {
        super(registered, eventType, eventControl);
    }

    public void write(long startTime, long duration, int id, String name) {
        EventWriter eventWriter;
        try {
            do {
                eventWriter= EventWriter.getEventWriter();
                if (!eventWriter.beginEvent(super.platformEventType)) {
                    break;
                }
                eventWriter.putLong(startTime);
                eventWriter.putLong(duration);
                eventWriter.putEventThread();
                eventWriter.putStackTrace();
                eventWriter.putInt(id);
                eventWriter.putString(name, this.stringPool3);
            } while(!eventWriter.endEvent());
        } catch (Throwable t) {
            EventWriter eventWriter2 = EventWriter.getEventWriter();
            if (eventWriter2 != null) {
                eventWriter2.reset();
            }
            throw t;
        }
    }
}