skip to content

A JVM service generates a large number of classes at run time and its start-up time is dominated by class loading. How does the bytecode verification phase factor into that cost, and what levers would you consider?

level: principalimportance: nice to knowfreq 22%

answer

  1. Verify cost ∝ bytecode size + branch targets
  2. Bootstrap classes trusted; app classes verified
  3. Fewer/smaller generated classes beats any flag
  4. CDS/AppCDS + AOT cache store linked form
  5. -Xverify:none ignored since JDK 18

basics

~20 s

Verification is per-class work proportional to bytecode size and branch structure, and it is a significant part of class-load cost for application classes — bootstrap classes are trusted and skipped by default. Levers: generate fewer and smaller classes, reuse them, and use class-data archives that store already-linked classes, rather than disabling verification, which modern JDKs ignore.

solid answer

~60 s

Verification runs once per class, and its cost scales with the code size and the number of branch merge points. HotSpot verifies classes loaded by application and custom loaders but trusts bootstrap-loader classes, so in a service that generates thousands of classes at run time, verification is real, attributable start-up work — alongside parsing and building the runtime representation. The levers worth considering, roughly in order of payoff: - **Generate less.** Reuse a generated class across shapes, or replace generated classes with method handles or a single interpreter-style implementation. Fewer classes beats faster verification of many. - **Generate smaller, simpler methods** — fewer branch targets means fewer frames to check. - **Use class-data archives.** Application class-data sharing stores parsed and verified classes in a mapped archive so the work is not repeated per JVM start; dynamic archives extend this to classes seen at run time. - **Emit correct stack map frames**, and weigh the generation-time cost of computing frames against the load-time cost of not having them. Disabling verification is not a lever: the flag was deprecated in JDK 13 and is ignored from JDK 18.

code

text · 9 lines
text
# what is being loaded, and by whom
java -Xlog:class+load=info:file=classes.log App

# create and reuse a dynamic application class-data archive
java -XX:ArchiveClassesAtExit=app.jsa -jar app.jar
java -XX:SharedArchiveFile=app.jsa -jar app.jar

# not available: the flag is ignored on modern JDKs
java -Xverify:none -jar app.jar   # warning; verification still runs

go deeper

for a junior

Know that every class the JVM loads has to be checked before it runs, and that loading thousands of generated classes therefore costs real time.

for a middle

Explain that verification is per class and scales with code size, and that bootstrap classes are not verified by default while application classes are.

for a senior

Measure with class-load logging or flight recording, then apply archives and generation hygiene, and know that disabling verification is no longer possible.

for a principal

Lead with the architectural question — how many classes must exist at all — and treat archives, frame-computation cost and generation strategy as follow-on tuning with operational obligations such as regenerating archives on each release.

## Where the time actually goes Class loading is several distinct pieces of work: locating and reading bytes, parsing the class file into runtime structures, **verifying**, preparing, and building method tables — plus the cost of any loader logic on top. Verification is a meaningful share of it, and unlike reading bytes it does not benefit from the page cache on a second start. The modern verifier is a linear pass that checks each instruction against the frames declared in the `StackMapTable`, so cost is roughly proportional to bytecode length, with extra work at branch targets and exception handlers where frames must be checked and merged. A service that generates one small class per request shape, per proxy, per mapper, per serializer can create thousands of classes and pay this thousands of times. One asymmetry matters: HotSpot distinguishes classes loaded by the bootstrap loader — trusted, not verified by default — from everything else, which is verified. So JDK classes are cheap on this axis and your generated classes are not. ## The levers, from most to least leverage **1. Generate fewer classes.** This dominates everything else because each class costs parsing, verification, metadata, method tables, and permanent metaspace footprint. Concretely: cache generated classes by shape rather than per instance; collapse near-identical generated classes into one parameterized implementation; replace a generated adapter with a `MethodHandle` chain or a hidden class where the runtime supports it; or fall back to a reflective or data-driven implementation for cold paths and generate only for the hot ones. "Generate lazily, and only where it pays" is usually a bigger win than any flag. **2. Generate simpler bytecode.** Long methods with many branches and exception handlers cost more to verify and more to compile later. Emitting several small methods, or avoiding elaborate generated control flow, reduces both. **3. Archive the linked form.** Class-data sharing maps a prepared archive of classes into memory so that parsing and verification are not repeated on every JVM start. Application class-data sharing extends this beyond JDK classes to the application's own classes, and dynamic archiving captures classes actually used in a run. Recent JDKs go further with ahead-of-time caches that store classes already loaded and linked from a training run. The relevant judgment is operational: archives must be regenerated when the application's classes change, and they help repeated start-ups, not a single one. **4. Get the frames right, and budget their computation.** Generated bytecode must carry a correct `StackMapTable` or it is rejected with `VerifyError` at load. Generation libraries can compute frames for you, but frame computation costs generation time — a real trade when classes are produced on the request path. Emitting frames yourself during generation, when the shape is known, avoids paying for a general analysis. **5. Not a lever: turning verification off.** `-Xverify:none` and `-noverify` were deprecated in JDK 13 and are ignored from JDK 18 onward. Even where they still worked, they removed the invariant that makes running arbitrary bytecode safe, in exchange for a shrinking benefit. ## How to decide Measure before choosing. Class-loading events in a flight recording, the count of loaded classes over time, and metaspace growth tell you whether generation volume, verification, or something else entirely (loader locking, resource lookup, initialization work) dominates. The answer is often that class *count* is the problem — in which case the architectural fix, generating fewer and reusing more, is both the largest win and the one that also reduces metaspace, JIT compilation and footprint. Flags and archives are the follow-up, not the opening move.

  • Why are JDK classes cheaper on this axis than application classes?
    HotSpot treats classes loaded by the bootstrap loader as trusted and does not verify them by default, while classes from application and custom loaders are verified. On top of that, JDK classes are the ones best covered by the default class-data sharing archive, so their parsed form is mapped in rather than rebuilt. Your generated classes get neither benefit unless you archive them yourself.
  • A team proposes computing stack map frames at generation time for every generated class. What is the trade?
    Correct frames are mandatory — without them the class is rejected with VerifyError — so the question is only how they are produced. Automatic frame computation performs a dataflow analysis over the generated method, which costs generation time and, if generation happens on a request path, latency. When the generator knows the shape of the code it emits, writing the frames directly is cheaper than asking a general algorithm to rediscover them.

saying these in an interview costs you the question

  • Proposing -Xverify:none as the fix on a modern JDK
  • Optimizing verification speed while ignoring the number of classes being generated
  • Assuming class-data archives capture arbitrary run-time generated classes automatically
  • Treating class loading as free because it is 'only start-up' in a service that generates classes continuously
  • Omitting stack map frames from generated bytecode to save time

context