From d7ad546758091ea16f836c9771ff5c03a32fb712 Mon Sep 17 00:00:00 2001 From: Stuart Marks Date: Wed, 8 Dec 2021 00:27:53 +0000 Subject: [PATCH] 8276422: Add command-line option to disable finalization Co-authored-by: David Holmes Co-authored-by: Brent Christian Reviewed-by: dholmes, kbarrett, bchristi --- make/data/hotspot-symbols/symbols-unix | 1 + .../share/classfile/classFileParser.cpp | 6 +- src/hotspot/share/include/jvm.h | 3 + src/hotspot/share/oops/instanceKlass.cpp | 1 + src/hotspot/share/oops/instanceKlass.hpp | 10 ++ src/hotspot/share/prims/jvm.cpp | 4 + src/hotspot/share/runtime/arguments.cpp | 12 ++ .../classes/java/lang/ref/Finalizer.java | 30 +++-- .../launcher/resources/launcher.properties | 5 +- .../share/native/libjava/Finalizer.c | 5 +- .../java/lang/Object/FinalizationOption.java | 122 ++++++++++++++++++ .../Object/InvalidFinalizationOption.java | 52 ++++++++ .../runtime/TestFinalizerStatisticsEvent.java | 31 ++++- 13 files changed, 265 insertions(+), 17 deletions(-) create mode 100644 test/jdk/java/lang/Object/FinalizationOption.java create mode 100644 test/jdk/java/lang/Object/InvalidFinalizationOption.java diff --git a/make/data/hotspot-symbols/symbols-unix b/make/data/hotspot-symbols/symbols-unix index d735f61b3a4..9ec0c1ec7c7 100644 --- a/make/data/hotspot-symbols/symbols-unix +++ b/make/data/hotspot-symbols/symbols-unix @@ -147,6 +147,7 @@ JVM_IsArrayClass JVM_IsCDSDumpingEnabled JVM_IsConstructorIx JVM_IsDumpingClassList +JVM_IsFinalizationEnabled JVM_IsHiddenClass JVM_IsInterface JVM_IsPrimitiveClass diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index a7ae7bef5c3..368e3534184 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -2835,7 +2835,8 @@ Method* ClassFileParser::parse_method(const ClassFileStream* const cfs, annotation_default_length, CHECK_NULL); - if (name == vmSymbols::finalize_method_name() && + if (InstanceKlass::is_finalization_enabled() && + name == vmSymbols::finalize_method_name() && signature == vmSymbols::void_method_signature()) { if (m->is_empty_method()) { _has_empty_finalizer = true; @@ -4171,7 +4172,8 @@ void ClassFileParser::set_precomputed_flags(InstanceKlass* ik) { bool f = false; const Method* const m = ik->lookup_method(vmSymbols::finalize_method_name(), vmSymbols::void_method_signature()); - if (m != NULL && !m->is_empty_method()) { + if (InstanceKlass::is_finalization_enabled() && + (m != NULL) && !m->is_empty_method()) { f = true; } diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 7783b00841d..e4f56c552b7 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -759,6 +759,9 @@ JVM_SupportsCX8(void); JNIEXPORT void JNICALL JVM_ReportFinalizationComplete(JNIEnv *env, jobject finalizee); +JNIEXPORT jboolean JNICALL +JVM_IsFinalizationEnabled(JNIEnv *env); + /************************************************************************* PART 2: Support for the Verifier and Class File Format Checker ************************************************************************/ diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index b4ee9c5dc0b..fafc58fa9fd 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -141,6 +141,7 @@ #endif // ndef DTRACE_ENABLED +bool InstanceKlass::_finalization_enabled = true; static inline bool is_class_loader(const Symbol* class_name, const ClassFileParser& parser) { diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index c2ad122e92b..82e918d1444 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -329,7 +329,17 @@ class InstanceKlass: public Klass { static bool _disable_method_binary_search; + // Controls finalizer registration + static bool _finalization_enabled; + public: + + // Queries finalization state + static bool is_finalization_enabled() { return _finalization_enabled; } + + // Sets finalization state + static void set_finalization_enabled(bool val) { _finalization_enabled = val; } + // The three BUILTIN class loader types bool is_shared_boot_class() const { return (_misc_flags & _misc_is_shared_boot_class) != 0; diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index de4e35492c8..f1d00c5da97 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -690,6 +690,10 @@ JVM_ENTRY(void, JVM_ReportFinalizationComplete(JNIEnv * env, jobject finalizee)) MANAGEMENT_ONLY(FinalizerService::on_complete(JNIHandles::resolve_non_null(finalizee), THREAD);) JVM_END +JVM_ENTRY(jboolean, JVM_IsFinalizationEnabled(JNIEnv * env)) + return InstanceKlass::is_finalization_enabled(); +JVM_END + // java.io.File /////////////////////////////////////////////////////////////// JVM_LEAF(char*, JVM_NativePath(char* path)) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 969c8e82b91..8b32e89e834 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -40,6 +40,7 @@ #include "logging/logStream.hpp" #include "logging/logTag.hpp" #include "memory/allocation.inline.hpp" +#include "oops/instanceKlass.hpp" #include "oops/oop.inline.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/arguments.hpp" @@ -2887,6 +2888,17 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_m if (FLAG_SET_CMDLINE(ErrorFileToStdout, true) != JVMFlag::SUCCESS) { return JNI_EINVAL; } + } else if (match_option(option, "--finalization=", &tail)) { + if (strcmp(tail, "enabled") == 0) { + InstanceKlass::set_finalization_enabled(true); + } else if (strcmp(tail, "disabled") == 0) { + InstanceKlass::set_finalization_enabled(false); + } else { + jio_fprintf(defaultStream::error_stream(), + "Invalid finalization value '%s', must be 'disabled' or 'enabled'.\n", + tail); + return JNI_EINVAL; + } } else if (match_option(option, "-XX:+ExtendedDTraceProbes")) { #if defined(DTRACE_ENABLED) if (FLAG_SET_CMDLINE(ExtendedDTraceProbes, true) != JVMFlag::SUCCESS) { diff --git a/src/java.base/share/classes/java/lang/ref/Finalizer.java b/src/java.base/share/classes/java/lang/ref/Finalizer.java index d5838b7a6b1..18aedf11bb3 100644 --- a/src/java.base/share/classes/java/lang/ref/Finalizer.java +++ b/src/java.base/share/classes/java/lang/ref/Finalizer.java @@ -61,9 +61,17 @@ final class Finalizer extends FinalReference { /* Package-private; must return queue; } + static final boolean ENABLED = isFinalizationEnabled(); + + private static native boolean isFinalizationEnabled(); + /* Invoked by VM */ static void register(Object finalizee) { - new Finalizer(finalizee); + if (ENABLED) { + new Finalizer(finalizee); + } else { + throw new InternalError("unexpected call to Finalizer::register when finalization is disabled"); + } } private void runFinalizer(JavaLangAccess jla) { @@ -130,7 +138,7 @@ final class Finalizer extends FinalReference { /* Package-private; must /* Called by Runtime.runFinalization() */ static void runFinalization() { - if (VM.initLevel() == 0) { + if (VM.initLevel() == 0 || ! ENABLED) { return; } @@ -182,14 +190,16 @@ final class Finalizer extends FinalReference { /* Package-private; must } static { - ThreadGroup tg = Thread.currentThread().getThreadGroup(); - for (ThreadGroup tgn = tg; - tgn != null; - tg = tgn, tgn = tg.getParent()); - Thread finalizer = new FinalizerThread(tg); - finalizer.setPriority(Thread.MAX_PRIORITY - 2); - finalizer.setDaemon(true); - finalizer.start(); + if (ENABLED) { + ThreadGroup tg = Thread.currentThread().getThreadGroup(); + for (ThreadGroup tgn = tg; + tgn != null; + tg = tgn, tgn = tg.getParent()); + Thread finalizer = new FinalizerThread(tg); + finalizer.setPriority(Thread.MAX_PRIORITY - 2); + finalizer.setDaemon(true); + finalizer.start(); + } } } diff --git a/src/java.base/share/classes/sun/launcher/resources/launcher.properties b/src/java.base/share/classes/sun/launcher/resources/launcher.properties index efcc4d69969..22fe4a35a5e 100644 --- a/src/java.base/share/classes/sun/launcher/resources/launcher.properties +++ b/src/java.base/share/classes/sun/launcher/resources/launcher.properties @@ -194,7 +194,10 @@ java.launcher.X.usage=\n\ \ override or augment a module with classes and resources\n\ \ in JAR files or directories.\n\ \ --source \n\ -\ set the version of the source in source-file mode.\n\n\ +\ set the version of the source in source-file mode.\n\ +\ --finalization=\n\ +\ controls finalization\n\ +\ is one of "enabled" or "disabled"\n\n\ These extra options are subject to change without notice.\n # Translators please note do not translate the options themselves diff --git a/src/java.base/share/native/libjava/Finalizer.c b/src/java.base/share/native/libjava/Finalizer.c index d0b81f63d6e..063e330ac9b 100644 --- a/src/java.base/share/native/libjava/Finalizer.c +++ b/src/java.base/share/native/libjava/Finalizer.c @@ -32,4 +32,7 @@ Java_java_lang_ref_Finalizer_reportComplete(JNIEnv* env, jclass cls, jobject fin JVM_ReportFinalizationComplete(env, finalizee); } - +JNIEXPORT jboolean JNICALL +Java_java_lang_ref_Finalizer_isFinalizationEnabled(JNIEnv* env, jclass cls) { + return JVM_IsFinalizationEnabled(env); +} diff --git a/test/jdk/java/lang/Object/FinalizationOption.java b/test/jdk/java/lang/Object/FinalizationOption.java new file mode 100644 index 00000000000..7d50412e26f --- /dev/null +++ b/test/jdk/java/lang/Object/FinalizationOption.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2021, 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. + */ + +/* + * @test + * @bug 8276422 + * @summary add command-line option to disable finalization + * @run main/othervm FinalizationOption yes + * @run main/othervm --finalization=enabled FinalizationOption yes + * @run main/othervm --finalization=disabled FinalizationOption no + */ +public class FinalizationOption { + static volatile boolean finalizerWasCalled = false; + + @SuppressWarnings("deprecation") + protected void finalize() { + finalizerWasCalled = true; + } + + static void create() { + new FinalizationOption(); + } + + /** + * Checks whether the finalizer thread is or is not running. The finalizer thread + * is a thread in the root thread group whose named is "Finalizer". + * @param expected boolean indicating whether a finalizer thread should exist + * @return boolean indicating whether the expectation was met + */ + static boolean checkFinalizerThread(boolean expected) { + ThreadGroup root = Thread.currentThread().getThreadGroup(); + for (ThreadGroup parent = root; + parent != null; + root = parent, parent = root.getParent()) + ; + + int nt = 100; + Thread[] threads; + while (true) { + threads = new Thread[nt]; + nt = root.enumerate(threads); + if (nt < threads.length) + break; + threads = new Thread[nt + 100]; + } + + Thread ft = null; + for (int i = 0; i < nt; i++) { + if ("Finalizer".equals(threads[i].getName())) { + ft = threads[i]; + break; + } + } + + String msg = (ft == null) ? "(none)" : ft.toString(); + boolean passed = (ft != null) == expected; + System.out.printf("Finalizer thread. Expected: %s Actual: %s %s%n", + expected, msg, passed ? "Passed." : "FAILED!"); + return passed; + } + + /** + * Checks whether there was a call to the finalize() method. + * @param expected boolean whether finalize() should be called + * @return boolean indicating whether the expecation was met + */ + static boolean checkFinalizerCalled(boolean expected) { + create(); + for (int i = 0; i < 100; i++) { + System.gc(); + try { + Thread.sleep(10L); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + if (finalizerWasCalled) { + break; + } + } + boolean passed = (expected == finalizerWasCalled); + System.out.printf("Call to finalize(). Expected: %s Actual: %s %s%n", + expected, finalizerWasCalled, + passed ? "Passed." : "FAILED!"); + return passed; + } + + public static void main(String[] args) { + boolean finalizationEnabled = switch (args[0]) { + case "yes" -> true; + case "no" -> false; + default -> { + throw new AssertionError("usage: FinalizationOption yes|no"); + } + }; + + boolean threadPass = checkFinalizerThread(finalizationEnabled); + boolean calledPass = checkFinalizerCalled(finalizationEnabled); + + if (!threadPass || !calledPass) + throw new AssertionError("Test failed."); + } +} diff --git a/test/jdk/java/lang/Object/InvalidFinalizationOption.java b/test/jdk/java/lang/Object/InvalidFinalizationOption.java new file mode 100644 index 00000000000..c5cca549ead --- /dev/null +++ b/test/jdk/java/lang/Object/InvalidFinalizationOption.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021, 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. + */ + +/* + * @test + * @bug 8276422 + * @summary Invalid/missing values for the finalization option should be rejected + * @library /test/lib + * @run driver InvalidFinalizationOption + */ + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +public class InvalidFinalizationOption { + public static void main(String[] args) throws Exception { + record TestData(String arg, String expected) { } + + TestData[] testData = { + new TestData("--finalization", "Unrecognized option"), + new TestData("--finalization=", "Invalid finalization value"), + new TestData("--finalization=azerty", "Invalid finalization value") + }; + + for (var data : testData) { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(data.arg); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldContain(data.expected); + output.shouldHaveExitValue(1); + } + } +} diff --git a/test/jdk/jdk/jfr/event/runtime/TestFinalizerStatisticsEvent.java b/test/jdk/jdk/jfr/event/runtime/TestFinalizerStatisticsEvent.java index ecec7c383fb..8256a62b571 100644 --- a/test/jdk/jdk/jfr/event/runtime/TestFinalizerStatisticsEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestFinalizerStatisticsEvent.java @@ -34,17 +34,20 @@ import jdk.test.lib.jfr.TestClassLoader; /** * @test + * @bug 8266936 8276422 * @summary The test verifies that classes overriding finalize() are represented as events. * @key jfr * @requires vm.hasJFR * @library /test/lib /test/jdk * @run main/othervm -Xlog:class+unload,finalizer -Xmx16m jdk.jfr.event.runtime.TestFinalizerStatisticsEvent + * @run main/othervm -Xlog:class+unload,finalizer -Xmx16m --finalization=disabled jdk.jfr.event.runtime.TestFinalizerStatisticsEvent disabled */ public final class TestFinalizerStatisticsEvent { private final static String TEST_CLASS_NAME = "jdk.jfr.event.runtime.TestFinalizerStatisticsEvent$TestClassOverridingFinalize"; private final static String TEST_CLASS_UNLOAD_NAME = "jdk.jfr.event.runtime.TestFinalizerStatisticsEvent$TestClassUnloadOverridingFinalize"; private final static String EVENT_PATH = EventNames.FinalizerStatistics; + private static boolean disabled = false; // Declare as public static to prevent the compiler from optimizing away all unread writes public static TestClassLoader unloadableClassLoader; @@ -52,6 +55,10 @@ public final class TestFinalizerStatisticsEvent { public static Object overridingInstance; public static void main(String[] args) throws Throwable { + if (args.length > 0 && "disabled".equals(args[0])) { + disabled = true; + System.out.println("Testing with finalization disabled"); + } Recording recording1 = new Recording(); recording1.enable(EVENT_PATH); Recording recording2 = new Recording(); @@ -69,8 +76,12 @@ public final class TestFinalizerStatisticsEvent { recording1.stop(); // rotation writes an event for TEST_CLASS_NAME into recording1 which now has 4 events reflecting this test case (3 chunks + 1 unload) try { - verify(recording2); - verify(recording1); + if (disabled) { + verifyDisabled(recording1); + } else { + verifyEnabled(recording2); + verifyEnabled(recording1); + } } finally { recording2.close(); @@ -84,7 +95,8 @@ public final class TestFinalizerStatisticsEvent { System.gc(); } - private static void verify(Recording recording) throws Throwable { + /* Verify correct operation with finalization enabled */ + private static void verifyEnabled(Recording recording) throws Throwable { boolean foundTestClassName = false; boolean foundTestClassUnloadName = false; List events = Events.fromRecording(recording); @@ -108,6 +120,19 @@ public final class TestFinalizerStatisticsEvent { Asserts.assertTrue(foundTestClassUnloadName, "The class: " + TEST_CLASS_UNLOAD_NAME + " overriding finalize() is not found"); } + /* Verify no jdk.FinalizerStatistics events with finalization disabled */ + private static void verifyDisabled(Recording recording) throws Throwable { + int f10nEvents = 0; + List events = Events.fromRecording(recording); + for (RecordedEvent event : events) { + System.out.println("Event:" + event); + if ("jdk.FinalizerStatistics".equals(event.getEventType().getName())) { + f10nEvents++; + } + } + Asserts.assertEquals(f10nEvents, 0, "Finalization disabled, but recorded " + f10nEvents + " jdk.FinalizerStatistics events"); + } + static public class TestClassOverridingFinalize { public boolean finalized = false;