@@ -119,15 +119,6 @@ JarTreeShakeBuildItem run() {
119119 * during static initialization or construction of reachable classes.
120120 */
121121 private Set <String > analyzeClassLoadingChains (Set <String > reachable ) {
122- // Build combined bytecode map. Order matters: app bytecode first, then dep bytecode
123- // (which includes transformed versions), then generated. This ensures transformed
124- // app class bytecode takes priority over the original, matching the lookup order
125- // in lookupBytecode().
126- Map <String , Supplier <byte []>> allBytecode = new HashMap <>();
127- allBytecode .putAll (input .appBytecode );
128- allBytecode .putAll (input .depBytecode );
129- allBytecode .putAll (input .generatedBytecode );
130-
131122 // Build the transformed bytecode map once (optimization B)
132123 Map <String , Supplier <byte []>> transformedBytecode = new HashMap <>();
133124 for (String name : input .transformedClassNames ) {
@@ -137,7 +128,7 @@ private Set<String> analyzeClassLoadingChains(Set<String> reachable) {
137128 }
138129 }
139130
140- ClassLoadingChainAnalyzer analyzer = new ClassLoadingChainAnalyzer (allBytecode , input .classToDep .keySet ());
131+ ClassLoadingChainAnalyzer analyzer = new ClassLoadingChainAnalyzer (input . allBytecode , input .classToDep .keySet ());
141132 Set <String > executedEntryPoints = new HashSet <>();
142133 Set <String > allPhase3Discovered = new HashSet <>();
143134 Set <String > classesToScan = new HashSet <>(reachable );
@@ -198,6 +189,7 @@ private Set<String> traceReachableClasses(Set<String> startingRoots, Set<String>
198189 }
199190 boolean sisuActivated = false ;
200191 final Map <ArtifactKey , Integer > depDeserializationFlags = new HashMap <>();
192+ final BfsScanResult scan = new BfsScanResult ();
201193
202194 while (!queue .isEmpty ()) {
203195 String name = queue .poll ();
@@ -212,12 +204,15 @@ private Set<String> traceReachableClasses(Set<String> startingRoots, Set<String>
212204
213205 // Single-pass ASM scan: extracts all class references, string class refs,
214206 // ServiceLoader calls, sisu detection, and resource/OIS detection in one pass
207+ scan .clear ();
215208 boolean isGenerated = input .generatedBytecode .containsKey (name );
216- BfsScanResult scan = scanBytecode (name , bytecode , allKnownClasses , isGenerated , sisuActivated );
209+ scanBytecode (name , bytecode , allKnownClasses , isGenerated , sisuActivated , scan );
217210
218- // Enqueue discovered class references
211+ // Enqueue discovered class references — only those we have bytecode for.
212+ // Skips JDK/platform classes that we'll never find, avoiding thousands of
213+ // no-op queue iterations and wasted lookups.
219214 for (String ref : scan .refs ) {
220- if (ref != null && visited .add (ref )) {
215+ if (ref != null && input . allBytecode . containsKey ( ref ) && visited .add (ref )) {
221216 queue .add (ref );
222217 }
223218 }
@@ -334,27 +329,11 @@ private void includeServiceProviders(String name, Set<String> visited, Queue<Str
334329 }
335330
336331 /**
337- * Looks up bytecode for a class: generated classes first, then dependency classes
338- * (which include transformed versions), then app classes.
339- * Dependency bytecode is checked before app bytecode because
340- * {@link JarTreeShakerInput#collectTransformedClasses} places transformed app class
341- * bytecode in {@code depBytecode}, and the transformed version may contain additional
342- * references (e.g., converter classes added by bytecode transformers) that the original
343- * app bytecode does not have.
332+ * Looks up bytecode for a class from the merged bytecode map.
344333 * Returns null if the class is a JDK class or not available.
345334 */
346335 private byte [] lookupBytecode (String name ) {
347- byte [] bytecode = getBytecodeOrNull (input .generatedBytecode .get (name ));
348- if (bytecode == null ) {
349- bytecode = getBytecodeOrNull (input .depBytecode .get (name ));
350- if (bytecode == null ) {
351- bytecode = getBytecodeOrNull (input .appBytecode .get (name ));
352- }
353- }
354- return bytecode ;
355- }
356-
357- private static byte [] getBytecodeOrNull (Supplier <byte []> supplier ) {
336+ Supplier <byte []> supplier = input .allBytecode .get (name );
358337 return supplier == null ? null : supplier .get ();
359338 }
360339
@@ -371,16 +350,23 @@ private static class BfsScanResult {
371350 boolean sisuDetected ;
372351 /** Flags for resource access and ObjectInputStream usage detection */
373352 int deserializationFlags ;
353+
354+ /** Resets all fields for reuse with the next class, avoiding per-class allocation. */
355+ void clear () {
356+ refs .clear ();
357+ serviceLoaderServices .clear ();
358+ sisuDetected = false ;
359+ deserializationFlags = 0 ;
360+ }
374361 }
375362
376363 /**
377364 * Single-pass ASM scan that combines reference extraction, string class matching,
378365 * ServiceLoader detection, sisu detection, and resource/ObjectInputStream detection.
379366 * Replaces 4-5 separate ClassReader.accept() calls with one.
380367 */
381- private BfsScanResult scanBytecode (String className , byte [] bytecode , Set <String > allKnownClasses ,
382- boolean isGenerated , boolean sisuAlreadyActivated ) {
383- BfsScanResult result = new BfsScanResult ();
368+ private void scanBytecode (String className , byte [] bytecode , Set <String > allKnownClasses ,
369+ boolean isGenerated , boolean sisuAlreadyActivated , BfsScanResult result ) {
384370 ClassReader reader = new ClassReader (bytecode );
385371 reader .accept (new ClassVisitor (Opcodes .ASM9 ) {
386372
@@ -625,7 +611,6 @@ public void visitEnd() {
625611 }
626612 }
627613 }, ClassReader .SKIP_FRAMES | ClassReader .SKIP_DEBUG );
628- return result ;
629614 }
630615
631616 /**
0 commit comments