View Javadoc
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  }