1 /*
2 * Copyright 2026 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.buffer;
17
18 import io.netty.util.internal.PlatformDependent;
19 import io.netty.util.internal.logging.InternalLogger;
20 import io.netty.util.internal.logging.InternalLoggerFactory;
21 import jdk.jfr.Event;
22 import jdk.jfr.FlightRecorder;
23 import jdk.jfr.FlightRecorderListener;
24
25 /**
26 * Registers the allocator events with JFR once a Flight Recorder has been initialized.
27 * <p>
28 * The events are {@link jdk.jfr.Registered @Registered(false)}, because registering the first event class sets up
29 * JFR's metadata repository, which is expensive and pointless in processes that never record. Until an event class
30 * is registered, JFR does not instrument it, so {@link Event#isEnabled()} is a constant {@code false} and the JIT
31 * removes the allocation path guards entirely. Registering it later retransforms the class, which deoptimizes the
32 * guards, exactly like starting a recording in a running process.
33 * <p>
34 * JFR notifies listeners while it holds the {@code PlatformRecorder} class lock. It registers its own JDK events
35 * under the same lock, and the event classes are loaded by this class's initializer and have trivial initializers,
36 * so registering from the callback does not wait for anything that may in turn wait for that lock.
37 * <p>
38 * A Flight Recorder is initialized at most once per JVM, so the listener removes itself after registering the events.
39 * Otherwise JFR's static listener list would keep this class, and with it the class loader that loaded Netty,
40 * reachable for the lifetime of the JVM. JFR iterates over a copy of the list, so this is safe from the callback.
41 * <p>
42 * If no Flight Recorder is ever initialized, the listener stays in that list, so the class loader that loaded Netty
43 * can't be unloaded, for example on a webapp redeploy. Holding the listener weakly doesn't help: before JDK 25,
44 * {@code addListener} stores the caller's {@code AccessControlContext} with it, which references Netty's protection
45 * domain and so its class loader. Set {@code -Dio.netty.jfr.enabled=false} to avoid this.
46 * <p>
47 * This must not be initialized from an event class initializer: registration initializes the event classes.
48 */
49 @SuppressWarnings("Since15")
50 final class JfrEventRegistration implements FlightRecorderListener {
51 private static final InternalLogger logger = InternalLoggerFactory.getInstance(JfrEventRegistration.class);
52
53 private static final Class<?>[] EVENT_CLASSES = {
54 AllocateChunkEvent.class,
55 FreeChunkEvent.class,
56 AllocateBufferEvent.class,
57 FreeBufferEvent.class,
58 ReallocateBufferEvent.class,
59 };
60
61 static {
62 try {
63 // Notifies the listener immediately if a recorder already exists, e.g. with -XX:StartFlightRecording.
64 FlightRecorder.addListener(new JfrEventRegistration());
65 } catch (Throwable t) {
66 logger.debug("Failed to register the JFR recorder listener, allocator events are disabled", t);
67 }
68 }
69
70 private JfrEventRegistration() {
71 }
72
73 /**
74 * Make sure the allocator events are registered once a Flight Recorder exists. Allocators call this, guarded by
75 * {@link PlatformDependent#isJfrEnabled()}, before they can emit events. The class initializer does the work.
76 */
77 static void init() {
78 }
79
80 @SuppressWarnings("unchecked")
81 @Override
82 public void recorderInitialized(FlightRecorder recorder) {
83 try {
84 for (Class<?> eventClass : EVENT_CLASSES) {
85 FlightRecorder.register((Class<? extends Event>) eventClass);
86 }
87 } catch (Throwable t) {
88 // Don't break the recording that is being started.
89 logger.debug("Failed to register the allocator JFR events", t);
90 } finally {
91 try {
92 FlightRecorder.removeListener(this);
93 } catch (Throwable t) {
94 logger.debug("Failed to remove the JFR recorder listener", t);
95 }
96 }
97 }
98 }