From ab75f234fdd4075947aea2895e7543e8275380cb Mon Sep 17 00:00:00 2001
Date: Sat, 23 May 2026 15:20:29 +0800
Subject: 8350621: Code Cache stops scheduling GC
src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 149 +++++++++----
src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 8 +
src/hotspot/share/gc/g1/g1CollectorState.hpp | 6 +
src/hotspot/share/gc/g1/g1Policy.cpp | 19 +-
src/hotspot/share/gc/g1/g1VMOperations.cpp | 9 +-
src/hotspot/share/gc/g1/g1VMOperations.hpp | 2 +
src/hotspot/share/gc/shared/gcCause.hpp | 5 +
.../TestCodeCacheUnloadDuringConcCycle.java | 207 ++++++++++++++++++
8 files changed, 353 insertions(+), 52 deletions(-)
create mode 100644 test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java
@@ -1776,6 +1776,66 @@ static bool gc_counter_less_than(uint x, uint y) {
#define LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, result) \
LOG_COLLECT_CONCURRENTLY(cause, "complete %s", BOOL_TO_STR(result))
+bool G1CollectedHeap::wait_full_mark_finished(GCCause::Cause cause,
+ uint old_marking_started_before,
+ uint old_marking_started_after,
+ uint old_marking_completed_after) {
+ // Request is finished if a full collection (concurrent or stw)
+ // was started after this request and has completed, e.g.
+ // started_before < completed_after.
+ if (gc_counter_less_than(old_marking_started_before,
+ old_marking_completed_after)) {
+ LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, true);
+ return true;
+ }
+
+ if (old_marking_started_after != old_marking_completed_after) {
+ // If there is an in-progress cycle (possibly started by us), then
+ // wait for that cycle to complete, e.g.
+ // while completed_now < started_after.
+ LOG_COLLECT_CONCURRENTLY(cause, "wait");
+ MonitorLocker ml(G1OldGCCount_lock);
+ while (gc_counter_less_than(_old_marking_cycles_completed,
+ old_marking_started_after)) {
+ ml.wait();
+ }
+ // Request is finished if the collection we just waited for was
+ // started after this request.
+ if (old_marking_started_before != old_marking_started_after) {
+ LOG_COLLECT_CONCURRENTLY(cause, "complete after wait");
+ return true;
+ }
+ }
+ return false;
+}
+
+// After calling wait_full_mark_finished(), this method determines whether we
+// previously failed for ordinary reasons (concurrent cycle in progress, whitebox
+// has control). Returns if this has been such an ordinary reason.
+static bool should_retry_vm_op(GCCause::Cause cause,
+ VM_G1TryInitiateConcMark* op) {
+ if (op->cycle_already_in_progress()) {
+ // If VMOp failed because a cycle was already in progress, it
+ // is now complete. But it didn't finish this user-requested
+ // GC, so try again.
+ LOG_COLLECT_CONCURRENTLY(cause, "retry after in-progress");
+ return true;
+ } else if (op->whitebox_attached()) {
+ // If WhiteBox wants control, wait for notification of a state
+ // change in the controller, then try again. Don't wait for
+ // release of control, since collections may complete while in
+ // control. Note: This won't recognize a STW full collection
+ // while waiting; we can't wait on multiple monitors.
+ LOG_COLLECT_CONCURRENTLY(cause, "whitebox control stall");
+ MonitorLocker ml(ConcurrentGCBreakpoints::monitor());
+ if (ConcurrentGCBreakpoints::is_controlled()) {
+ ml.wait();
+ }
+ return true;
+ }
+ return false;
+}
+
bool G1CollectedHeap::try_collect_concurrently(GCCause::Cause cause,
uint gc_counter,
uint old_marking_started_before) {
@@ -1836,7 +1896,45 @@ bool G1CollectedHeap::try_collect_concurrently(GCCause::Cause cause,
LOG_COLLECT_CONCURRENTLY(cause, "ignoring STW full GC");
old_marking_started_before = old_marking_started_after;
}
+ } else if (GCCause::is_codecache_requested_gc(cause)) {
+ // For a CodeCache requested GC, before marking, progress is ensured as the
+ // following Remark pause unloads code (and signals the requester such).
+ // Otherwise we must ensure that it is restarted.
+ //
+ // For a CodeCache requested GC, a successful GC operation means that
+ // (1) marking is in progress. I.e. the VMOp started the marking or a
+ // Remark pause is pending from a different VM op; we will potentially
+ // abort a mixed phase if needed.
+ // (2) a new cycle was started (by this thread or some other), or
+ // (3) a Full GC was performed.
+ //
+ // Cases (2) and (3) are detected together by a change to
+ // _old_marking_cycles_started.
+ //
+ // Compared to other "automatic" GCs (see below), we do not consider being
+ // in whitebox as sufficient too because we might be anywhere within that
+ // cycle and we need to make progress.
+ if (op.mark_in_progress() ||
+ (old_marking_started_before != old_marking_started_after)) {
+ LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, true);
+ return true;
+ }
+
+ if (wait_full_mark_finished(cause,
+ old_marking_started_before,
+ old_marking_started_after,
+ old_marking_completed_after)) {
+ return true;
+ }
+
+ if (should_retry_vm_op(cause, &op)) {
+ continue;
+ }
} else if (!GCCause::is_user_requested_gc(cause)) {
+ assert(cause == GCCause::_g1_humongous_allocation ||
+ cause == GCCause::_g1_periodic_collection,
+ "Unsupported cause %s", GCCause::to_string(cause));
+
// For an "automatic" (not user-requested) collection, we just need to
// ensure that progress is made.
//
@@ -1848,11 +1946,6 @@ bool G1CollectedHeap::try_collect_concurrently(GCCause::Cause cause,
// (5) a Full GC was performed.
// Cases (4) and (5) are detected together by a change to
// _old_marking_cycles_started.
- //
- // Note that (1) does not imply (4). If we're still in the mixed
- // phase of an earlier concurrent collection, the request to make the
- // collection a concurrent start won't be honored. If we don't check for
- // both conditions we'll spin doing back-to-back collections.
if (op.gc_succeeded() ||
op.cycle_already_in_progress() ||
op.whitebox_attached() ||
@@ -1876,56 +1969,20 @@ bool G1CollectedHeap::try_collect_concurrently(GCCause::Cause cause,
BOOL_TO_STR(op.gc_succeeded()),
old_marking_started_before, old_marking_started_after);
- // Request is finished if a full collection (concurrent or stw)
- // was started after this request and has completed, e.g.
- // started_before < completed_after.
- if (gc_counter_less_than(old_marking_started_before,
- old_marking_completed_after)) {
- LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, true);
+ if (wait_full_mark_finished(cause,
+ old_marking_started_before,
+ old_marking_started_after,
+ old_marking_completed_after)) {
return true;
}
- if (old_marking_started_after != old_marking_completed_after) {
- // If there is an in-progress cycle (possibly started by us), then
- // wait for that cycle to complete, e.g.
- // while completed_now < started_after.
- LOG_COLLECT_CONCURRENTLY(cause, "wait");
- MonitorLocker ml(G1OldGCCount_lock);
- while (gc_counter_less_than(_old_marking_cycles_completed,
- old_marking_started_after)) {
- ml.wait();
- }
- // Request is finished if the collection we just waited for was
- // started after this request.
- if (old_marking_started_before != old_marking_started_after) {
- LOG_COLLECT_CONCURRENTLY(cause, "complete after wait");
- return true;
- }
- }
-
// If VMOp was successful then it started a new cycle that the above
// wait &etc should have recognized as finishing this request. This
// differs from a non-user-request, where gc_succeeded does not imply
// a new cycle was started.
assert(!op.gc_succeeded(), "invariant");
- if (op.cycle_already_in_progress()) {
- // If VMOp failed because a cycle was already in progress, it
- // is now complete. But it didn't finish this user-requested
- // GC, so try again.
- LOG_COLLECT_CONCURRENTLY(cause, "retry after in-progress");
- continue;
- } else if (op.whitebox_attached()) {
- // If WhiteBox wants control, wait for notification of a state
- // change in the controller, then try again. Don't wait for
- // release of control, since collections may complete while in
- // control. Note: This won't recognize a STW full collection
- // while waiting; we can't wait on multiple monitors.
- LOG_COLLECT_CONCURRENTLY(cause, "whitebox control stall");
- MonitorLocker ml(ConcurrentGCBreakpoints::monitor());
- if (ConcurrentGCBreakpoints::is_controlled()) {
- ml.wait();
- }
+ if (should_retry_vm_op(cause, &op)) {
continue;
}
}
@@ -279,6 +279,14 @@ private:
// (e) cause == _g1_periodic_collection and +G1PeriodicGCInvokesConcurrent.
bool should_do_concurrent_full_gc(GCCause::Cause cause);
+ // Wait until a full mark (either currently in progress or one that completed
+ // after the current request) has finished. Returns whether that full mark started
+ // after this request. If so, we typically do not need another one.
+ bool wait_full_mark_finished(GCCause::Cause cause,
+ uint old_marking_started_before,
+ uint old_marking_started_after,
+ uint old_marking_completed_after);
+
// Attempt to start a concurrent cycle with the indicated cause.
// precondition: should_do_concurrent_full_gc(cause)
bool try_collect_concurrently(GCCause::Cause cause,
@@ -60,6 +60,9 @@ class G1CollectorState {
// do the concurrent start phase work.
volatile bool _initiate_conc_mark_if_possible;
+ // Marking is in progress. Set from start of the concurrent start pause to the
+ // end of the Remark pause.
+ bool _mark_in_progress;
// Marking or rebuilding remembered set work is in progress. Set from the end
// of the concurrent start pause to the end of the Cleanup pause.
bool _mark_or_rebuild_in_progress;
@@ -78,6 +81,7 @@ public:
_in_concurrent_start_gc(false),
_initiate_conc_mark_if_possible(false),
+ _mark_in_progress(false),
_mark_or_rebuild_in_progress(false),
_clearing_bitmap(false),
_in_full_gc(false) { }
@@ -92,6 +96,7 @@ public:
void set_initiate_conc_mark_if_possible(bool v) { _initiate_conc_mark_if_possible = v; }
+ void set_mark_in_progress(bool v) { _mark_in_progress = v; }
void set_mark_or_rebuild_in_progress(bool v) { _mark_or_rebuild_in_progress = v; }
void set_clearing_bitmap(bool v) { _clearing_bitmap = v; }
@@ -106,6 +111,7 @@ public:
bool initiate_conc_mark_if_possible() const { return _initiate_conc_mark_if_possible; }
+ bool mark_in_progress() const { return _mark_in_progress; }
bool mark_or_rebuild_in_progress() const { return _mark_or_rebuild_in_progress; }
bool clearing_bitmap() const { return _clearing_bitmap; }
@@ -564,6 +564,7 @@ void G1Policy::record_full_collection_end() {
collector_state()->set_in_young_gc_before_mixed(false);
collector_state()->set_initiate_conc_mark_if_possible(need_to_start_conc_mark("end of Full GC"));
collector_state()->set_in_concurrent_start_gc(false);
+ collector_state()->set_mark_in_progress(false);
collector_state()->set_mark_or_rebuild_in_progress(false);
collector_state()->set_clearing_bitmap(false);
@@ -669,6 +670,7 @@ void G1Policy::record_concurrent_mark_remark_end() {
double elapsed_time_ms = (end_time_sec - _mark_remark_start_sec)*1000.0;
_analytics->report_concurrent_mark_remark_times_ms(elapsed_time_ms);
record_pause(G1GCPauseType::Remark, _mark_remark_start_sec, end_time_sec);
+ collector_state()->set_mark_in_progress(false);
}
void G1Policy::record_concurrent_mark_cleanup_start() {
@@ -888,6 +890,7 @@ void G1Policy::record_young_collection_end(bool concurrent_operation_is_full_mar
assert(!(G1GCPauseTypeHelper::is_concurrent_start_pause(this_pause) && collector_state()->mark_or_rebuild_in_progress()),
"If the last pause has been concurrent start, we should not have been in the marking window");
if (G1GCPauseTypeHelper::is_concurrent_start_pause(this_pause)) {
+ collector_state()->set_mark_in_progress(concurrent_operation_is_full_mark);
collector_state()->set_mark_or_rebuild_in_progress(concurrent_operation_is_full_mark);
}
@@ -1184,6 +1187,17 @@ void G1Policy::initiate_conc_mark() {
collector_state()->set_initiate_conc_mark_if_possible(false);
}
+static const char* requester_for_mixed_abort(GCCause::Cause cause) {
+ if (cause == GCCause::_wb_breakpoint) {
+ return "run_to breakpoint";
+ } else if (GCCause::is_codecache_requested_gc(cause)) {
+ return "codecache";
+ } else {
+ assert(G1CollectedHeap::heap()->is_user_requested_concurrent_full_gc(cause), "must be");
+ return "user";
+ }
+}
+
void G1Policy::decide_on_concurrent_start_pause() {
// We are about to decide on whether this pause will be a
// concurrent start pause.
@@ -1216,8 +1230,7 @@ void G1Policy::decide_on_concurrent_start_pause() {
initiate_conc_mark();
log_debug(gc, ergo)("Initiate concurrent cycle (concurrent cycle initiation requested)");
} else if (_g1h->is_user_requested_concurrent_full_gc(cause) ||
- (cause == GCCause::_codecache_GC_threshold) ||
- (cause == GCCause::_codecache_GC_aggressive) ||
+ GCCause::is_codecache_requested_gc(cause) ||
(cause == GCCause::_wb_breakpoint)) {
// Initiate a concurrent start. A concurrent start must be a young only
// GC, so the collector state must be updated to reflect this.
@@ -1232,7 +1245,7 @@ void G1Policy::decide_on_concurrent_start_pause() {
abort_time_to_mixed_tracking();
initiate_conc_mark();
log_debug(gc, ergo)("Initiate concurrent cycle (%s requested concurrent cycle)",
- (cause == GCCause::_wb_breakpoint) ? "run_to breakpoint" : "user");
+ requester_for_mixed_abort(cause));
} else {
// The concurrent marking thread is still finishing up the
// previous cycle. If we start one right now the two cycles
@@ -59,6 +59,7 @@ VM_G1TryInitiateConcMark::VM_G1TryInitiateConcMark(uint gc_count_before,
GCCause::Cause gc_cause) :
VM_GC_Operation(gc_count_before, gc_cause),
_transient_failure(false),
+ _mark_in_progress(false),
_cycle_already_in_progress(false),
_whitebox_attached(false),
_terminating(false),
@@ -85,6 +86,9 @@ void VM_G1TryInitiateConcMark::doit() {
// Record for handling by caller.
_terminating = g1h->concurrent_mark_is_terminating();
+ _mark_in_progress = g1h->collector_state()->mark_in_progress();
+ _cycle_already_in_progress = g1h->concurrent_mark()->cm_thread()->in_progress();
+
if (_terminating && GCCause::is_user_requested_gc(_gc_cause)) {
// When terminating, the request to initiate a concurrent cycle will be
// ignored by do_collection_pause_at_safepoint; instead it will just do
@@ -93,9 +97,8 @@ void VM_G1TryInitiateConcMark::doit() {
// requests the alternative GC might still be needed.
} else if (!g1h->policy()->force_concurrent_start_if_outside_cycle(_gc_cause)) {
// Failure to force the next GC pause to be a concurrent start indicates
- // there is already a concurrent marking cycle in progress. Set flag
- // to notify the caller and return immediately.
- _cycle_already_in_progress = true;
+ // there is already a concurrent marking cycle in progress. Flags to indicate
+ // that were already set, so return immediately.
} else if ((_gc_cause != GCCause::_wb_breakpoint) &&
ConcurrentGCBreakpoints::is_controlled()) {
// WhiteBox wants to be in control of concurrent cycles, so don't try to
@@ -50,6 +50,7 @@ public:
class VM_G1TryInitiateConcMark : public VM_GC_Operation {
bool _transient_failure;
+ bool _mark_in_progress;
bool _cycle_already_in_progress;
bool _whitebox_attached;
bool _terminating;
@@ -62,6 +63,7 @@ public:
virtual bool doit_prologue();
virtual void doit();
bool transient_failure() const { return _transient_failure; }
+ bool mark_in_progress() const { return _mark_in_progress; }
bool cycle_already_in_progress() const { return _cycle_already_in_progress; }
bool whitebox_attached() const { return _whitebox_attached; }
bool terminating() const { return _terminating; }
@@ -109,6 +109,11 @@ class GCCause : public AllStatic {
cause == GCCause::_heap_dump);
}
+ inline static bool is_codecache_requested_gc(GCCause::Cause cause) {
+ return (cause == _codecache_GC_threshold ||
+ cause == _codecache_GC_aggressive);
+ }
+
// Causes for collection of the tenured gernation
inline static bool is_tenured_allocation_failure_gc(GCCause::Cause cause) {
// _adaptive_size_policy for a full collection after a young GC
new file mode 100644
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package gc.g1;
+
+/*
+ * @test TestCodeCacheUnloadDuringConcCycle
+ * @bug 8350621
+ * @summary Test to make sure that code cache unloading does not hang when receiving
+ * a request to unload code cache during concurrent mark.
+ * We do that by triggering a code cache gc request (by triggering compilations)
+ * during concurrent mark, and verify that after the concurrent cycle additional code
+ * cache gc requests start more concurrent cycles.
+ * @requires vm.gc.G1
+ * @library /test/lib /testlibrary /
+ * @modules java.base/jdk.internal.misc
+ * java.management
+ * @build jdk.test.whitebox.WhiteBox
+ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
+ * @run main/othervm -Xmx20M -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. gc.g1.TestCodeCacheUnloadDuringConcCycle
+ */
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryPoolMXBean;
+import java.lang.management.MemoryUsage;
+import java.lang.reflect.Field;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jdk.test.lib.Asserts;
+import jdk.test.lib.Platform;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.whitebox.WhiteBox;
+
+public class TestCodeCacheUnloadDuringConcCycle {
+ public static final String AFTER_FIRST_CYCLE_MARKER = "Marker for this test";
+
+ private static final WhiteBox WB = WhiteBox.getWhiteBox();
+
+ private static OutputAnalyzer runTest(String concPhase) throws Exception {
+ OutputAnalyzer output = ProcessTools.executeLimitedTestJava("-XX:+UseG1GC",
+ "-Xmx20M",
+ "-XX:+UnlockDiagnosticVMOptions",
+ "-Xbootclasspath/a:.",
+ "-Xlog:gc=trace,codecache",
+ "-XX:+WhiteBoxAPI",
+ "-XX:ReservedCodeCacheSize=" + (Platform.is32bit() ? "4M" : "8M"),
+ "-XX:StartAggressiveSweepingAt=50",
+ "-XX:CompileCommand=compileonly,gc.g1.SomeClass::*",
+ "-XX:CompileCommand=compileonly,gc.g1.Foo*::*",
+ TestCodeCacheUnloadDuringConcCycleRunner.class.getName(),
+ concPhase);
+ return output;
+ }
+
+ private static void runAndCheckTest(String test) throws Exception {
+ OutputAnalyzer output;
+
+ output = runTest(test);
+ output.shouldHaveExitValue(0);
+ System.out.println(output.getStdout());
+
+ String[] parts = output.getStdout().split(AFTER_FIRST_CYCLE_MARKER);
+
+ // Either "Threshold" or "Aggressive" CodeCache GC are fine for the test.
+ final String codecacheGCStart = "Pause Young (Concurrent Start) (CodeCache GC ";
+
+ boolean success = parts.length == 2 && parts[1].indexOf(codecacheGCStart) != -1;
+ Asserts.assertTrue(success, "Could not find a CodeCache GC Threshold GC after finishing the concurrent cycle");
+ }
+
+ private static void allTests() throws Exception {
+ runAndCheckTest(WB.BEFORE_MARKING_COMPLETED);
+ runAndCheckTest(WB.G1_BEFORE_REBUILD_COMPLETED);
+ runAndCheckTest(WB.G1_BEFORE_CLEANUP_COMPLETED);
+ }
+
+ public static void main(String[] args) throws Exception {
+ allTests();
+ }
+}
+
+class TestCodeCacheUnloadDuringConcCycleRunner {
+ private static final WhiteBox WB = WhiteBox.getWhiteBox();
+
+ private static void refClass(Class clazz) throws Exception {
+ Field name = clazz.getDeclaredField("NAME");
+ name.setAccessible(true);
+ name.get(null);
+ }
+
+ private static class MyClassLoader extends URLClassLoader {
+ public MyClassLoader(URL url) {
+ super(new URL[]{url}, null);
+ }
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ try {
+ return super.loadClass(name, resolve);
+ } catch (ClassNotFoundException e) {
+ return Class.forName(name, resolve, TestCodeCacheUnloadDuringConcCycleRunner.class.getClassLoader());
+ }
+ }
+ }
+
+ private static void triggerCodeCacheGC() throws Exception {
+ URL url = TestCodeCacheUnloadDuringConcCycleRunner.class.getProtectionDomain().getCodeSource().getLocation();
+
+ try {
+ int i = 0;
+ do {
+ ClassLoader cl = new MyClassLoader(url);
+ refClass(cl.loadClass("gc.g1.SomeClass"));
+
+ if (i % 20 == 0) {
+ System.out.println("Compiled " + i + " classes");
+ }
+ i++;
+ } while (i < 200);
+ System.out.println("Compilation done, compiled " + i + " classes");
+ } catch (Throwable t) {
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Running to breakpoint: " + args[0]);
+ try {
+ WB.concurrentGCAcquireControl();
+ WB.concurrentGCRunTo(args[0]);
+
+ System.out.println("Try to trigger code cache GC");
+
+ triggerCodeCacheGC();
+
+ WB.concurrentGCRunToIdle();
+ } finally {
+ // Make sure that the marker we use to find the expected log message is printed
+ // before we release whitebox control, i.e. before the expected garbage collection
+ // can start.
+ System.out.println(TestCodeCacheUnloadDuringConcCycle.AFTER_FIRST_CYCLE_MARKER);
+ WB.concurrentGCReleaseControl();
+ }
+ Thread.sleep(1000);
+ triggerCodeCacheGC();
+ }
+}
+
+abstract class Foo {
+ public abstract int foo();
+}
+
+class Foo1 extends Foo {
+ private int a;
+ public int foo() { return a; }
+}
+
+class Foo2 extends Foo {
+ private int a;
+ public int foo() { return a; }
+}
+
+class Foo3 extends Foo {
+ private int a;
+ public int foo() { return a; }
+}
+
+class Foo4 extends Foo {
+ private int a;
+ public int foo() { return a; }
+}
+
+class SomeClass {
+ static final String NAME = "name";
+
+ static {
+ int res =0;
+ Foo[] foos = new Foo[] { new Foo1(), new Foo2(), new Foo3(), new Foo4() };
+ for (int i = 0; i < 100000; i++) {
+ res = foos[i % foos.length].foo();
+ }
+ }
+}
--
2.17.1