Does wrapping a Java field in a getter method make code slower at run time? Explain what the JVM does with a one-line accessor in hot code.
answer
- Interpreted: real call; compiled: gone
- Trivial-method threshold, ~5-byte getter
- Warm-up property, not source property
- Megamorphic site = not inlined
- final on the method buys nothing
basics
~20 sIn the interpreter the getter really is a call. Once the code is hot and JIT-compiled, a tiny accessor is inlined and the call disappears, leaving just the field load. Caveats: it only applies after warm-up, and only if the JIT can pin the call to one target.
solid answer
~50 sDuring interpretation the accessor costs a real invoke. But an accessor is the easiest possible inlining candidate: its bytecode is a few bytes long, well under HotSpot's trivial-method threshold, so once the enclosing code is compiled the body is pasted in and the machine code contains the same field load direct field access would produce. Two honest caveats. First, this is a warm-up property, not a source property: short-lived or rarely executed code runs interpreted and does pay. Second, inlining needs a target. A getter declared on an interface whose call site has seen many implementation classes can become megamorphic, and then it is dispatched, not inlined — and everything downstream of it loses optimization too. So write the getter for encapsulation. `final` on the method does not make it faster in any measurable way; the JIT already devirtualizes when it can.
code
text · 8 lines// javap -c
public int getX();
0: aload_0
1: getfield #7 // Field x:I
4: ireturn // 5 bytes total
// -XX:+PrintInlining
@ 4 Point::getX (5 bytes) accessorgo deeper
Answer that the JIT inlines tiny accessors once the code is hot, so the call disappears and only the field read remains; keep the getter for encapsulation.
Add the two conditions: it requires warm-up, and it requires the call site to resolve to one target.
Bring up profile pollution — an interface accessor shared across many implementations can go megamorphic and block inlining plus everything downstream — and verify with PrintInlining.
Use this to set a team norm: keep the encapsulation, and spend attention instead on the dispatch shape of inner-loop call sites, which is where the abstraction actually stops being free.
## The two worlds a Java method lives in Every Java method starts life interpreted: the JVM walks its bytecode one instruction at a time. In that world an accessor is genuinely a call — push a frame, execute `aload_0; getfield; areturn`, pop the frame. That is why measurements of very short-lived programs can show accessors costing something. Once a method becomes hot, the JIT compiles it, and at that point the accessor is the most inlinable thing in the language. HotSpot treats very small methods specially: a body of a handful of bytecodes is inlined essentially unconditionally (the trivial-method threshold, `-XX:MaxTrivialSize`, defaults to 6 bytes; a plain getter's body is 5). `PrintInlining` even labels these decisions `accessor`. After inlining, the generated code holds a single load from the object's field offset — exactly what direct field access would emit. ## Why this is a property of the run, not of the source The collapse happens only where the code got compiled. Code that runs a few hundred times, code in a CLI tool that exits in 200 ms, or a cold path inside an otherwise hot service, all execute interpreted or in a lightly optimizing tier and do pay for the call. This is the honest answer to "is the abstraction free": it is free in steady state, not universally. ## The condition that can break it Inlining requires the compiler to know which method body to paste. For a getter reached through an interface or an overridable method, the JIT uses class-hierarchy information and the recorded receiver-type profile to pick a target, guarding a speculative choice with a cheap class check. If the call site has observed many different receiver classes, it becomes megamorphic: the JVM dispatches through a table instead of inlining, and the surrounding optimizations that depended on seeing the body are lost as well. A getter is cheap; a megamorphic getter in an inner loop is not. ## Things that do not help Marking the method `final` is a common cargo-cult fix. It can only ever help the compiler decide faster; it does not unlock a speed-up the JIT would not already find via class-hierarchy analysis, and it certainly does not help a site that is megamorphic because many *distinct* final implementations flow through it. Making the field `public` to "skip the call" trades encapsulation for nothing in compiled code. ## How to check rather than believe Run with `-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining` and look for the accessor line. If it says `inline (hot)` or `accessor`, the call is gone. If it says `no static binding` or the site is reported megamorphic, you have found a real issue — and the fix is about the dispatch shape of the call site, not about the getter itself.
- If accessors are inlined anyway, when would you still see them in a CPU profile?When the code has not been compiled yet — short-running processes, start-up paths, or methods below the compilation threshold — and when the call site cannot be devirtualized, for example an interface getter that has seen many implementation classes. Sampling profilers can also attribute inlined frames back to the source method, so a name in the profile does not by itself prove a call happened.
- Does declaring the getter final make it faster?Not meaningfully. HotSpot already devirtualizes calls with a single loaded implementation using class-hierarchy analysis, and speculates with a guard otherwise. final removes nothing from the generated code and does not rescue a megamorphic call site.
saying these in an interview costs you the question
- "Getters are always slower, so expose the field" — untrue for compiled code
- Assuming inlining applies from the first invocation, ignoring interpretation and warm-up
- Adding final everywhere as a performance measure
- Believing a getter is inlined even at a call site that has seen a dozen implementation classes