After evaluating multiple approaches, we chose to use a new frame type (FRAME_NATIVE_REMOTE = 7) instead of a custom modifier flag. This eliminates:
- ✅ All varint encoding overhead
- ✅ Any potential conflicts with Java modifiers
- ✅ Ambiguity in semantics
Remote native frames now use:
- Modifier:
0x0100(ACC_NATIVE, same as regular native frames) —Lookup::fillRemoteFrameInfo()inflightRecorder.cpp - Frame Type:
FRAME_NATIVE_REMOTE(7) —enum FrameTypeIdinframe.h, registered as"Native (remote)"byRecording::writeFrameTypes()
History note: the "Profiler Custom Modifiers" and "Modifier Conflict Analysis" sections below record the rejected modifier-flag design. No custom modifier flag (
ACC_REMOTE_SYMBOLICATIONor otherwise) exists in the code.
The Java Virtual Machine specification defines the following access modifiers for classes, methods, and fields:
| Modifier | Value | Applies To | Description |
|---|---|---|---|
| ACC_PUBLIC | 0x0001 | All | Public access |
| ACC_PRIVATE | 0x0002 | Methods/Fields | Private access |
| ACC_PROTECTED | 0x0004 | Methods/Fields | Protected access |
| ACC_STATIC | 0x0008 | Methods/Fields | Static member |
| ACC_FINAL | 0x0010 | All | Final/non-overridable |
| ACC_SYNCHRONIZED | 0x0020 | Methods | Synchronized method |
| ACC_SUPER | 0x0020 | Classes | Treat superclass invokes specially |
| ACC_BRIDGE | 0x0040 | Methods | Compiler-generated bridge method |
| ACC_VOLATILE | 0x0040 | Fields | Volatile field |
| ACC_VARARGS | 0x0080 | Methods | Variable arity method |
| ACC_TRANSIENT | 0x0080 | Fields | Not serialized |
| ACC_NATIVE | 0x0100 | Methods | Native implementation |
| ACC_INTERFACE | 0x0200 | Classes | Interface declaration |
| ACC_ABSTRACT | 0x0400 | Classes/Methods | Abstract class/method |
| ACC_STRICT | 0x0800 | Methods | Use strict floating-point |
| ACC_SYNTHETIC | 0x1000 | All | Compiler-generated |
| ACC_ANNOTATION | 0x2000 | Classes | Annotation type |
| ACC_ENUM | 0x4000 | Classes/Fields | Enum type/constant |
| ACC_MANDATED | 0x8000 | Parameters | Implicitly declared |
The modifier-flag design considered the following custom flags. Only the reused Java modifiers are actually emitted (native frames use 0x100); ACC_REMOTE_SYMBOLICATION was never adopted:
| Modifier | Value | Usage | Notes |
|---|---|---|---|
| ACC_NATIVE | 0x0100 | Native frames | Reuses Java's ACC_NATIVE for consistency |
| ACC_SYNTHETIC | 0x1000 | Compiler-generated | Reuses Java's ACC_SYNTHETIC |
| ACC_BRIDGE | 0x0040 | Bridge methods | Reuses Java's ACC_BRIDGE |
| ACC_REMOTE_SYMBOLICATION | 0x10000 | Remote native frames | Custom profiler flag (bit 16, outside Java range) |
Version 1 (Initial): Used 0x200
- ❌ CONFLICT: Java's
ACC_INTERFACE(0x0200) - Issues: Could confuse JFR parsers, clash with standard modifiers
Version 2: Changed to 0x2000
⚠️ CONFLICT: Java'sACC_ANNOTATION(0x2000)- While theoretically safe for methods (annotations only apply to classes), still within Java's reserved range
Version 3: Changed to 0x10000 (bit 16) — later superseded by the frame-type approach
- ✅ NO CONFLICTS: Completely outside Java's standard modifier range (0x0001-0x8000)
- ✅ Clean separation from JVM specification
- ✅ Future-proof against new Java modifiers
Java Modifier Range:
- Java uses bits 0-15 (0x0001 to 0x8000)
- Highest standard modifier:
ACC_MANDATED = 0x8000(bit 15)
Custom Profiler Range:
- Bits 16-30 available for custom flags (0x10000 to 0x40000000)
0x10000(bit 16) is first bit outside Java range- Clean power of 2, easy to test and debug
Benefits:
- Zero theoretical conflicts with any Java modifier (current or future)
- Clear separation between JVM standard (bits 0-15) and profiler custom (bits 16+)
- 32-bit safe: Well within
jintrange (signed 32-bit) - JFR compatible:
_modifiersfield supports full 32-bit values - Extensible: Room for additional custom flags (0x20000, 0x40000, etc.)
JFR uses LEB128 variable-length encoding for modifiers. The encoding size depends on the value:
| Value Range | Example | Bytes | Notes |
|---|---|---|---|
| 0x00-0x7F | 0 | 1 | Most compact |
| 0x80-0x3FFF | 0x0100 (ACC_NATIVE), 0x1000 (ACC_SYNTHETIC) | 2 | Standard native frames |
| 0x4000-0x1FFFFF | 0x8000 (ACC_MANDATED), 0x10000 | 3 | +1 byte overhead! |
Critical insight: Using 0x10000 would add 1 extra byte per remote native method-pool entry. Modifiers are written once per entry in the method constant pool (writeMethodEntry() in flightRecorder.cpp), not per stack frame, so the cost scales with the number of distinct remote frames (build-id + pc offset) per chunk.
-
Use modifier 0x0200 (bit 9):
- ❌ Conflicts with ACC_INTERFACE
-
Use modifier 0x2000 (bit 13):
- ❌ Conflicts with ACC_ANNOTATION (theoretically)
⚠️ Would be safe in practice (annotations only for classes)
-
Use modifier 0x10000 (bit 16):
- ❌ 3-byte varint encoding vs 2-byte for regular frames
- ❌ +1 byte overhead per method-pool entry
-
Use a separate field:
- ❌ Would require JFR metadata changes
- ❌ Breaks backward compatibility
-
Use frame type FRAME_NATIVE_REMOTE (CHOSEN):
- ✅ Zero encoding overhead (the frame type is already written as one byte per stack frame by
Recording::writeStackTraces()) - ✅ No modifier conflicts
- ✅ Clear semantics
- ✅ Zero encoding overhead (the frame type is already written as one byte per stack frame by
When adding custom modifiers in the future:
- Check JVM Spec: Always verify against latest JVM specification
- Consider Context: Modifiers for methods vs classes vs fields
- Document Clearly: Explain why the bit is safe to use
- Test Compatibility: Verify JFR parsers handle custom modifiers correctly
- Java Virtual Machine Specification (JVMS §4.1, §4.5, §4.6)
- JFR Format Specification
- Original Implementation: [elfBuildId.cpp, flightRecorder.cpp]