8276422: Add command-line option to disable finalization

Co-authored-by: David Holmes <dholmes@openjdk.org>
Co-authored-by: Brent Christian <bchristi@openjdk.org>
Reviewed-by: dholmes, kbarrett, bchristi
This commit is contained in:
Stuart Marks 2021-12-08 00:27:53 +00:00
parent ec7cb6d5d3
commit d7ad546758
13 changed files with 265 additions and 17 deletions

View File

@ -147,6 +147,7 @@ JVM_IsArrayClass
JVM_IsCDSDumpingEnabled
JVM_IsConstructorIx
JVM_IsDumpingClassList
JVM_IsFinalizationEnabled
JVM_IsHiddenClass
JVM_IsInterface
JVM_IsPrimitiveClass

View File

@ -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;
}

View File

@ -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
************************************************************************/

View File

@ -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) {

View File

@ -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;

View File

@ -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))

View File

@ -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) {

View File

@ -61,9 +61,17 @@ final class Finalizer extends FinalReference<Object> { /* 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<Object> { /* 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<Object> { /* 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();
}
}
}

View File

@ -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 <version>\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=<value>\n\
\ controls finalization\n\
\ <value> 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

View File

@ -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);
}

View File

@ -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.");
}
}

View File

@ -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);
}
}
}

View File

@ -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<RecordedEvent> 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<RecordedEvent> 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;