Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/hotspot/share/classfile/classFileParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2250,6 +2250,7 @@ void ClassFileParser::copy_method_annotations(ConstMethod* cm,

Method* ClassFileParser::parse_method(const ClassFileStream* const cfs,
bool is_interface,
bool is_identity_class,
const ConstantPool* cp,
bool* const has_localvariable_table,
TRAPS) {
Expand Down Expand Up @@ -2826,7 +2827,8 @@ Method* ClassFileParser::parse_method(const ClassFileStream* const cfs,

if (InstanceKlass::is_finalization_enabled() &&
name == vmSymbols::finalize_method_name() &&
signature == vmSymbols::void_method_signature()) {
signature == vmSymbols::void_method_signature() &&
is_identity_class) {
if (m->is_empty_method()) {
_has_empty_finalizer = true;
} else {
Expand Down Expand Up @@ -2866,6 +2868,7 @@ void ClassFileParser::parse_methods(const ClassFileStream* const cfs,
for (int index = 0; index < length; index++) {
Method* method = parse_method(cfs,
is_interface,
is_identity_class(),
_cp,
has_localvariable_table,
CHECK);
Expand Down Expand Up @@ -4157,7 +4160,8 @@ void ClassFileParser::set_precomputed_flags(InstanceKlass* ik) {
const Method* const m = ik->lookup_method(vmSymbols::finalize_method_name(),
vmSymbols::void_method_signature());
if (InstanceKlass::is_finalization_enabled() &&
(m != nullptr) && !m->is_empty_method()) {
(m != nullptr) && !m->is_empty_method() &&
m->method_holder()->access_flags().is_identity_class()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we call ClassFileParser's is_identity_class() here instead of going through the method_holder?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fred pointed out my proposed change actually changes the meaning as my proposal checks the current class, not the class that defines the finalize method. This is different for a finalize method from an abstract value class

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A finalizer in an abstract value class should probably still be registered for identity subclasses; as otherwise, changing an abstract identity class with a finalizer to an abstract value class with a finalizer would be a breaking change as long as finalizers continue to exist.

f = true;
}

Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/classfile/classFileParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ class ClassFileParser {
// Method parsing
Method* parse_method(const ClassFileStream* const cfs,
bool is_interface,
bool is_identity_class,
const ConstantPool* cp,
bool* const has_localvariable_table,
TRAPS);
Expand Down
1 change: 0 additions & 1 deletion src/hotspot/share/prims/jvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,6 @@ JVM_ENTRY(jarray, JVM_CopyOfSpecialArray(JNIEnv *env, jarray orig, jint from, ji
array = dst;
} else {
const ArrayProperties props = ArrayProperties::Default().with_null_restricted(ak->is_null_free_array_klass());

array = oopFactory::new_objArray(vk, len, props, CHECK_NULL);
int end = to < oh()->length() ? to : oh()->length();
for (int i = from; i < end; i++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright (c) 2026, 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 TestFinalizableValues
* @library /test/lib
* @enablePreview
* @run main TestFinalizableValues
*/

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;

public class TestFinalizableValues {


public static class TestHelper {
static boolean finalizer1WasCalled = false;
static boolean finalizer2WasCalled = false;

static value class MyVal {
static volatile boolean finalizerWasCalled = false;
int i = 0;

@SuppressWarnings("deprecation")
protected void finalize() {
finalizerWasCalled = true;
}
}

static abstract value class MyAbstractVal {
int i = 0;

@SuppressWarnings("deprecation")
protected void finalize() {
finalizer1WasCalled = true;
}
}

static value class MyVal2 extends MyAbstractVal {}

static class MyId extends MyAbstractVal {}

static class MyId2 extends MyAbstractVal {
int i = 0;

@SuppressWarnings("deprecation")
protected void finalize() {
finalizer2WasCalled = true;
}
}

static void create(Class<?> c) {
try {
c.newInstance();
} catch(InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

public static void main(String[] args) throws ClassNotFoundException {
Class<?> c = Class.forName(args[0]);
boolean expectFinalizer1 = Boolean.valueOf(args[1]);
boolean expectFinalizer2 = Boolean.valueOf(args[2]);

create(c);
for (int i = 0; i < 100; i++) {
System.gc();
try {
Thread.sleep(10L);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
// if (finalizer1WasCalled) {
// break;
// }
}
if (finalizer1WasCalled != expectFinalizer1) {
throw new RuntimeException("Finalizer1 was "
+ (finalizer1WasCalled ? "" : "not ")
+ "executed");
}
if (finalizer2WasCalled != expectFinalizer2) {
throw new RuntimeException("Finalizer2 was "
+ (finalizer2WasCalled ? "" : "not ")
+ "executed");
}
}
}

static void test(String classname, boolean expectRegistration, String... args) throws IOException {
List<String> argsList = new ArrayList<>();
Collections.addAll(argsList, "--enable-preview");
Collections.addAll(argsList, "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED");
Collections.addAll(argsList, "-Dtest.class.path=" + System.getProperty("test.class.path", "."));
Collections.addAll(argsList, "-XX:+TraceFinalizerRegistration");
Collections.addAll(argsList, "TestFinalizableValues$TestHelper");
Collections.addAll(argsList, "TestFinalizableValues$TestHelper$" + classname);
Collections.addAll(argsList, args);
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(argsList);
OutputAnalyzer out = new OutputAnalyzer(pb.start());
if (expectRegistration) {
out.shouldContain("as finalizable");
} else {
out.shouldNotContain("as finalizable");
}
out.shouldHaveExitValue(0);
}
public static void main(String[] args) throws IOException {
test("MyVal", false, "false", "false");
test("MyVal2", false, "false", "false");
test("MyId", false, "false", "false");
test("MyId2", true, "false", "true");
}
}