Implement Virtual Thread and Continuation API
It makes the following API functional:
Thread.startVirtualThread(Runnable)
Thread.ofVirtual()
The following APIs should be Virtual-Thread-compatible:
Thread.sleep
LockSupport.park*
ReentrantLock
The feature is behind the virtual_thread_impl_v1 flag
Flag: com.android.art.flags.virtual_thread_impl_v1
Bug: 346542404
Test: 2394-continuation-yields
Test: 2395-virtual-thread-reentrantlock
Test: 2395-virtual-thread-sleep-api
Test: CtsLibcoreOjTestCases:java.lang.Thread.virtual
Change-Id: I15734380d27c11fb23b73890995df41f3d963dab
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 79246ca..bdf1aa7 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -376,6 +376,7 @@
"native/java_lang_reflect_Proxy.cc",
"native/java_util_concurrent_atomic_AtomicLong.cc",
"native/jdk_internal_misc_Unsafe.cc",
+ "native/jdk_internal_vm_Continuation.cc",
"native/libcore_io_Memory.cc",
"native/libcore_util_CharsetUtils.cc",
"native/org_apache_harmony_dalvik_ddmc_DdmServer.cc",
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 31b8986..cf6a2e6 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -1376,7 +1376,15 @@
void FillVirtualThreadFrame(Thread* self, ShadowFrame* frame) {
ScopedAssertNoThreadSuspension ns("No thread suspension when filling virtual thread frame)");
ObjPtr<mirror::Object> jpeer = self->GetPeer();
- ObjPtr<mirror::Object> v_context = WellKnownClasses::java_lang_Thread_target->GetObject(jpeer);
+ ObjPtr<mirror::Object> v_context;
+ if (self->AreVirtualThreadFlagsEnabled(kContinuation)) {
+ ObjPtr<mirror::Object> cont = WellKnownClasses::java_lang_Thread_cont->GetObject(jpeer);
+ DCHECK(!cont.IsNull());
+ v_context =
+ WellKnownClasses::jdk_internal_vm_Continuation_virtualThreadContext->GetObject(cont);
+ } else {
+ v_context = WellKnownClasses::java_lang_Thread_target->GetObject(jpeer);
+ }
DCHECK(v_context->GetClass()->DescriptorEquals("Ldalvik/system/VirtualThreadContext;"))
<< frame->GetMethod()->PrettyMethod();
ObjPtr<mirror::Object> parked_states =
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index ea6ff4e..f129a52 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -261,6 +261,7 @@
VirtualThreadPark(soa.Decode<mirror::Object>(v_context),
soa.Decode<mirror::Object>(parked_states),
soa.Decode<mirror::Throwable>(vm_error),
+ false,
reason);
}
diff --git a/runtime/native/jdk_internal_vm_Continuation.cc b/runtime/native/jdk_internal_vm_Continuation.cc
new file mode 100644
index 0000000..26a44fa
--- /dev/null
+++ b/runtime/native/jdk_internal_vm_Continuation.cc
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jdk_internal_vm_Continuation.h"
+
+#include "art_method-inl.h"
+#include "dex/invoke_type.h"
+#include "handle_scope-inl.h"
+#include "jni.h"
+#include "mirror/object.h"
+#include "mirror/throwable.h"
+#include "native/native_util.h"
+#include "nativehelper/jni_macros.h"
+#include "obj_ptr-inl.h"
+#include "obj_ptr.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "scoped_thread_state_change.h"
+#include "thread.h"
+#include "virtual_thread_common.h"
+#include "well_known_classes.h"
+
+namespace art HIDDEN {
+
+static jint Continuation_doYieldNative([[maybe_unused]] JNIEnv* env,
+ jobject,
+ jobject v_context,
+ jobject parked_states,
+ jobject vm_error) {
+ ScopedObjectAccess soa(env);
+
+ PinningReason reason = kNoReason;
+ bool success = VirtualThreadPark(soa.Decode<mirror::Object>(v_context),
+ soa.Decode<mirror::Object>(parked_states),
+ soa.Decode<mirror::Throwable>(vm_error),
+ true,
+ reason);
+
+ if (!success && reason == kNoReason) {
+ // Expect a pending exception, and return to the java level to handle it
+ DCHECK(Thread::Current()->IsExceptionPending());
+ return kNoReason;
+ }
+
+ return reason;
+}
+
+static void Continuation_enterSpecial(
+ JNIEnv* env, jobject, jobject cont, jboolean j_is_continue, jboolean j_is_virtual_thread) {
+ Thread* self = Thread::Current();
+ DCHECK(j_is_virtual_thread);
+ DCHECK(!self->AreVirtualThreadFlagsEnabled(kIsVirtual));
+
+ ScopedObjectAccess soa(env);
+ ObjPtr<mirror::Object> continuation = soa.Decode<mirror::Object>(cont);
+ ObjPtr<mirror::Object> v_context =
+ WellKnownClasses::jdk_internal_vm_Continuation_virtualThreadContext->GetObject(continuation);
+ ObjPtr<mirror::Object> parked_states =
+ WellKnownClasses::dalvik_system_VirtualThreadContext_parkedStates->GetObject(v_context);
+
+ bool is_continue = j_is_continue;
+ DCHECK_NE(parked_states.IsNull(), is_continue) << "Likely a bug in the Continuation.java";
+ if (parked_states.IsNull() == is_continue) {
+ const char* msg = is_continue ? "Can't continue without the saved stack"
+ : "saved stack shouldn't exist when a continuation start.";
+ self->ThrowNewException("Ljava/lang/IllegalStateException;", msg);
+ return;
+ }
+
+ uint8_t flags_mask = kIsVirtual | kContinuation | (is_continue ? kUnparking : 0);
+ self->SetVirtualThreadFlags(flags_mask, true);
+
+ WellKnownClasses::jdk_internal_vm_Continuation_enter->InvokeStatic<'V', 'L', 'Z'>(
+ self, continuation, is_continue);
+
+ // When a virtual thread is parked, clear the VirtualThreadParkingError used to
+ // unwind the native stack.
+ if (self->IsExceptionPending() &&
+ self->AreVirtualThreadFlagsEnabled(VirtualThreadFlag::kParking)) {
+ DCHECK(self->GetException()->GetClass()->DescriptorEquals(
+ "Ldalvik/system/VirtualThreadParkingError;"));
+ self->ClearException();
+ }
+
+ self->SetVirtualThreadFlags(kIsVirtual | kContinuation | kParking, false);
+}
+
+static JNINativeMethod gMethods[] = {
+ NATIVE_METHOD(
+ Continuation,
+ doYieldNative,
+ "(Ldalvik/system/VirtualThreadContext;"
+ "Ldalvik/system/VirtualThreadParkedStates;Ldalvik/system/VirtualThreadParkingError;)I"),
+ NATIVE_METHOD(Continuation, enterSpecial, "(Ljdk/internal/vm/Continuation;ZZ)V"),
+};
+
+void register_jdk_internal_vm_Continuation(JNIEnv* env) {
+ REGISTER_NATIVE_METHODS("jdk/internal/vm/Continuation");
+}
+
+} // namespace art
diff --git a/runtime/native/jdk_internal_vm_Continuation.h b/runtime/native/jdk_internal_vm_Continuation.h
new file mode 100644
index 0000000..1c0c904
--- /dev/null
+++ b/runtime/native/jdk_internal_vm_Continuation.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include "base/macros.h"
+
+namespace art HIDDEN {
+
+void register_jdk_internal_vm_Continuation(JNIEnv* env);
+
+} // namespace art
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 056e3e8..de32746 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -56,10 +56,10 @@
#include "base/aborting.h"
#include "base/arena_allocator.h"
#include "base/atomic.h"
+#include "base/calloc_arena_pool.h"
#include "base/dumpable.h"
#include "base/file_utils.h"
#include "base/flags.h"
-#include "base/calloc_arena_pool.h"
#include "base/mem_map_arena_pool.h"
#include "base/memory_tool.h"
#include "base/mutex.h"
@@ -146,6 +146,7 @@
#include "native/java_lang_reflect_Proxy.h"
#include "native/java_util_concurrent_atomic_AtomicLong.h"
#include "native/jdk_internal_misc_Unsafe.h"
+#include "native/jdk_internal_vm_Continuation.h"
#include "native/libcore_io_Memory.h"
#include "native/libcore_util_CharsetUtils.h"
#include "native/org_apache_harmony_dalvik_ddmc_DdmServer.h"
@@ -2460,6 +2461,7 @@
register_java_lang_VMClassLoader(env);
register_java_util_concurrent_atomic_AtomicLong(env);
register_jdk_internal_misc_Unsafe(env);
+ register_jdk_internal_vm_Continuation(env);
register_libcore_io_Memory(env);
register_libcore_util_CharsetUtils(env);
register_org_apache_harmony_dalvik_ddmc_DdmServer(env);
diff --git a/runtime/thread.h b/runtime/thread.h
index 1669973..6e4e420 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -207,10 +207,14 @@
enum VirtualThreadFlag : uint8_t {
// This flag is set only when a virtual thread is running on the given carrier thread.
kIsVirtual = 1u,
+ // This flag is set only when carrier thread enters a jdk.internal.vm.Continuation.
+ // In this case, the Continuation is the internal implementation details of virtual thread.
+ // Importantly, virtual thread frames are on top of the carrier thread frames.
+ kContinuation = 1u << 1,
// The flag is set when a virtual thread is being parked and unmounted from the carrier thread.
- kParking = 1u << 1,
+ kParking = 1u << 2,
// The flag is set when a virtual thread is being unparked and mounted from the carrier thread.
- kUnparking = 1u << 2,
+ kUnparking = 1u << 3,
};
// ART uses two types of ABI/code: quick and native.
diff --git a/runtime/virtual_thread_common.cc b/runtime/virtual_thread_common.cc
index ed5567f..228249b 100644
--- a/runtime/virtual_thread_common.cc
+++ b/runtime/virtual_thread_common.cc
@@ -41,23 +41,41 @@
namespace art HIDDEN {
+inline static ArtMethod* get_enter_method(bool is_continuation_api) {
+ return is_continuation_api ? WellKnownClasses::jdk_internal_vm_Continuation_enterSpecial
+ : nullptr;
+}
+
+inline static ArtMethod* get_park_method(bool is_continuation_api) {
+ if (is_continuation_api) {
+ return WellKnownClasses::jdk_internal_vm_Continuation_doYieldNative;
+ } else {
+ return WellKnownClasses::java_lang_Thread_parkVirtualInternal;
+ }
+}
+
struct VirtualThreadParkingVisitor final : public StackVisitor {
- explicit VirtualThreadParkingVisitor(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_)
+ VirtualThreadParkingVisitor(Thread* thread, bool is_continuation_api)
+ REQUIRES_SHARED(Locks::mutator_lock_)
: StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames, true),
+ enter_method_(get_enter_method(is_continuation_api)),
+ park_method_(get_park_method(is_continuation_api)),
shadow_frame_count_(0),
reason_(kNoReason) {}
bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
ShadowFrame* shadow_frame = GetCurrentShadowFrame();
if (shadow_frame == nullptr) {
- // Stack walking continues only if the only non-interpreted frame is
- // Thread.parkVirtualInternal until JIT and AOT frame is supported.
ArtMethod** quick_frame = GetCurrentQuickFrame();
ArtMethod* method = quick_frame != nullptr ? *quick_frame : nullptr;
if (method != nullptr && method->IsNative()) {
- if (method == WellKnownClasses::java_lang_Thread_parkVirtualInternal) {
- // Continue walking for this park method.
+ if (method == park_method_) {
+ // Stack walking continues only if the only non-interpreted frame is
+ // a known method parking the virtual thread / yielding the continuation.
return true;
+ } else if (method == enter_method_) {
+ // The rest of the stack belongs to the carrier thread.
+ return false;
}
reason_ = kNativeMethod;
@@ -93,6 +111,8 @@
reinterpret_cast<VirtualThreadParkingVisitor*>(visitor)->reason_ = kMonitor;
}
+ const ArtMethod* const enter_method_;
+ const ArtMethod* const park_method_;
std::vector<const ShadowFrame*> shadow_frames_;
size_t shadow_frame_count_;
PinningReason reason_;
@@ -101,8 +121,15 @@
bool VirtualThreadPark(ObjPtr<mirror::Object> v_context,
ObjPtr<mirror::Object> parked_states,
ObjPtr<mirror::Throwable> vm_error,
+ bool is_continuation_api,
PinningReason& reason_) {
Thread* self = Thread::Current();
+ if (self->AreVirtualThreadFlagsEnabled(kContinuation) != is_continuation_api) {
+ self->ThrowNewExceptionF("Ljava/lang/IllegalStateException;",
+ "unmatched kContinuation value when Virtual Thread is parking: %d",
+ is_continuation_api);
+ return false;
+ }
StackHandleScope<9> hs(self);
ClassLinker* cl = Runtime::Current()->GetClassLinker();
@@ -112,7 +139,7 @@
Handle<mirror::Object> opeer_h = hs.NewHandle(self->GetPeer());
Handle<mirror::Throwable> vm_error_h = hs.NewHandle(vm_error);
- VirtualThreadParkingVisitor dump_visitor(self);
+ VirtualThreadParkingVisitor dump_visitor(self, is_continuation_api);
dump_visitor.WalkStack();
DCHECK_NE(dump_visitor.reason_, kUnsupportedFrame) << "JIT / AOT frame isn't supported.";
diff --git a/runtime/virtual_thread_common.h b/runtime/virtual_thread_common.h
index 311be59..ac173b6 100644
--- a/runtime/virtual_thread_common.h
+++ b/runtime/virtual_thread_common.h
@@ -23,12 +23,16 @@
namespace art HIDDEN {
+// LINT.IfChange
enum PinningReason {
kNoReason = 0,
kNativeMethod = 3,
kMonitor = 4,
kUnsupportedFrame = 5,
};
+// Update Continuation.Pinned and Continuation.pinnedReason(int)
+// This lint check doesn't work across 2 git projects until b/154647410 is fixed.
+// LINT.ThenChange(../../libcore/ojluni/src/main/java/jdk/internal/vm/Continuation.java)
/**
* @return true if parking is successful. False if the thread is pinned, or fails to park.
@@ -36,6 +40,7 @@
bool VirtualThreadPark(ObjPtr<mirror::Object> v_context,
ObjPtr<mirror::Object> parked_states,
ObjPtr<mirror::Throwable> vm_error,
+ bool is_continuation_api,
PinningReason& reason_) REQUIRES_SHARED(Locks::mutator_lock_);
} // namespace art
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 1c189d4..9c3d26f 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -118,6 +118,9 @@
ArtMethod* WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D;
ArtMethod* WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F;
ArtMethod* WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars;
+ArtMethod* WellKnownClasses::jdk_internal_vm_Continuation_doYieldNative;
+ArtMethod* WellKnownClasses::jdk_internal_vm_Continuation_enter;
+ArtMethod* WellKnownClasses::jdk_internal_vm_Continuation_enterSpecial;
ArtMethod* WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation;
ArtMethod* WellKnownClasses::libcore_reflect_AnnotationMember_init;
ArtMethod* WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
@@ -146,6 +149,7 @@
ArtField* WellKnownClasses::java_lang_System_out;
ArtField* WellKnownClasses::java_lang_System_err;
ArtField* WellKnownClasses::java_lang_String_EMPTY;
+ArtField* WellKnownClasses::java_lang_Thread_cont;
ArtField* WellKnownClasses::java_lang_Thread_parkBlocker;
ArtField* WellKnownClasses::java_lang_Thread_daemon;
ArtField* WellKnownClasses::java_lang_Thread_group;
@@ -178,6 +182,7 @@
ArtField* WellKnownClasses::java_nio_ByteBuffer_offset;
ArtField* WellKnownClasses::java_util_Collections_EMPTY_LIST;
ArtField* WellKnownClasses::java_util_concurrent_ThreadLocalRandom_seeder;
+ArtField* WellKnownClasses::jdk_internal_vm_Continuation_virtualThreadContext;
ArtField* WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer;
ArtField* WellKnownClasses::jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image;
ArtField* WellKnownClasses::libcore_util_EmptyArray_STACK_TRACE_ELEMENT;
@@ -448,7 +453,7 @@
java_lang_Long_value = CacheValueInBoxField(
class_linker, self, "Ljava/lang/Long;", "J");
- StackHandleScope<49u> hs(self);
+ StackHandleScope<50u> hs(self);
Handle<mirror::Class> d_s_bdcl =
hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/BaseDexClassLoader;"));
Handle<mirror::Class> d_s_dlcl =
@@ -531,6 +536,8 @@
hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/util/concurrent/ThreadLocalRandom;"));
Handle<mirror::Class> j_u_f_c =
hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/util/function/Consumer;"));
+ Handle<mirror::Class> j_i_v_cont =
+ hs.NewHandle(FindSystemClass(class_linker, self, "Ljdk/internal/vm/Continuation;"));
Handle<mirror::Class> j_i_m_fd =
hs.NewHandle(FindSystemClass(class_linker, self, "Ljdk/internal/math/FloatingDecimal;"));
Handle<mirror::Class> j_i_m_fd_btab = hs.NewHandle(FindSystemClass(
@@ -767,6 +774,23 @@
pointer_size);
jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars =
CacheMethod(j_i_m_fd_btab.Get(), /*is_static=*/ false, "getChars", "([C)I", pointer_size);
+ jdk_internal_vm_Continuation_doYieldNative =
+ CacheMethod(j_i_v_cont.Get(),
+ /*is_static=*/true,
+ "doYieldNative",
+ "(Ldalvik/system/VirtualThreadContext;Ldalvik/system/"
+ "VirtualThreadParkedStates;Ldalvik/system/VirtualThreadParkingError;)I",
+ pointer_size);
+ jdk_internal_vm_Continuation_enter = CacheMethod(j_i_v_cont.Get(),
+ /*is_static=*/true,
+ "enter",
+ "(Ljdk/internal/vm/Continuation;Z)V",
+ pointer_size);
+ jdk_internal_vm_Continuation_enterSpecial = CacheMethod(j_i_v_cont.Get(),
+ /*is_static=*/true,
+ "enterSpecial",
+ "(Ljdk/internal/vm/Continuation;ZZ)V",
+ pointer_size);
libcore_reflect_AnnotationFactory_createAnnotation = CacheMethod(
l_r_af.Get(),
@@ -866,6 +890,8 @@
java_lang_System_err =
CacheField(ToClass(java_lang_System), /*is_static=*/ true, "err", "Ljava/io/PrintStream;");
+ java_lang_Thread_cont =
+ CacheField(j_l_Thread.Get(), /*is_static=*/false, "cont", "Ljdk/internal/vm/Continuation;");
java_lang_Thread_parkBlocker =
CacheField(j_l_Thread.Get(), /*is_static=*/ false, "parkBlocker", "Ljava/lang/Object;");
java_lang_Thread_daemon = CacheField(j_l_Thread.Get(), /*is_static=*/ false, "daemon", "Z");
@@ -928,6 +954,11 @@
java_util_concurrent_ThreadLocalRandom_seeder = CacheField(
j_u_c_tlr.Get(), /*is_static=*/ true, "seeder", "Ljava/util/concurrent/atomic/AtomicLong;");
+ jdk_internal_vm_Continuation_virtualThreadContext =
+ CacheField(j_i_v_cont.Get(),
+ /*is_static=*/false,
+ "virtualThreadContext",
+ "Ldalvik/system/VirtualThreadContext;");
jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer =
CacheField(j_i_m_fd_btab.Get(), /*is_static=*/ false, "buffer", "[C");
jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image = CacheField(
@@ -1052,6 +1083,9 @@
jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D = nullptr;
jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F = nullptr;
jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars = nullptr;
+ jdk_internal_vm_Continuation_doYieldNative = nullptr;
+ jdk_internal_vm_Continuation_enter = nullptr;
+ jdk_internal_vm_Continuation_enterSpecial = nullptr;
libcore_reflect_AnnotationFactory_createAnnotation = nullptr;
libcore_reflect_AnnotationMember_init = nullptr;
org_apache_harmony_dalvik_ddmc_DdmServer_broadcast = nullptr;
@@ -1071,6 +1105,7 @@
java_lang_System_in = nullptr;
java_lang_System_out = nullptr;
java_lang_System_err = nullptr;
+ java_lang_Thread_cont = nullptr;
java_lang_Thread_parkBlocker = nullptr;
java_lang_Thread_daemon = nullptr;
java_lang_Thread_group = nullptr;
@@ -1099,6 +1134,7 @@
java_nio_ByteBuffer_offset = nullptr;
java_util_Collections_EMPTY_LIST = nullptr;
java_util_concurrent_ThreadLocalRandom_seeder = nullptr;
+ jdk_internal_vm_Continuation_virtualThreadContext = nullptr;
jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer = nullptr;
jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image = nullptr;
libcore_util_EmptyArray_STACK_TRACE_ELEMENT = nullptr;
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index a3b456a..3bbdf03 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -162,6 +162,9 @@
static ArtMethod* jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D;
static ArtMethod* jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F;
static ArtMethod* jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars;
+ static ArtMethod* jdk_internal_vm_Continuation_doYieldNative;
+ static ArtMethod* jdk_internal_vm_Continuation_enter;
+ static ArtMethod* jdk_internal_vm_Continuation_enterSpecial;
static ArtMethod* libcore_reflect_AnnotationFactory_createAnnotation;
static ArtMethod* libcore_reflect_AnnotationMember_init;
static ArtMethod* org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
@@ -191,6 +194,7 @@
static ArtField* java_lang_System_in;
static ArtField* java_lang_System_out;
static ArtField* java_lang_System_err;
+ static ArtField* java_lang_Thread_cont;
static ArtField* java_lang_Thread_parkBlocker;
static ArtField* java_lang_Thread_daemon;
static ArtField* java_lang_Thread_group;
@@ -223,6 +227,7 @@
static ArtField* java_nio_ByteBuffer_offset;
static ArtField* java_util_Collections_EMPTY_LIST;
static ArtField* java_util_concurrent_ThreadLocalRandom_seeder;
+ static ArtField* jdk_internal_vm_Continuation_virtualThreadContext;
static ArtField* jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer;
static ArtField* jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image;
static ArtField* libcore_util_EmptyArray_STACK_TRACE_ELEMENT;
diff --git a/test/2394-continuation-yields/build.py b/test/2394-continuation-yields/build.py
new file mode 100644
index 0000000..d531de7
--- /dev/null
+++ b/test/2394-continuation-yields/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2025 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def build(ctx):
+ if ctx.jvm:
+ return # The test does not build on JVM
+ ctx.default_build()
diff --git a/test/2394-continuation-yields/expected-stderr.txt b/test/2394-continuation-yields/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2394-continuation-yields/expected-stderr.txt
diff --git a/test/2394-continuation-yields/expected-stdout.txt b/test/2394-continuation-yields/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2394-continuation-yields/expected-stdout.txt
diff --git a/test/2394-continuation-yields/info.txt b/test/2394-continuation-yields/info.txt
new file mode 100644
index 0000000..70263b5
--- /dev/null
+++ b/test/2394-continuation-yields/info.txt
@@ -0,0 +1 @@
+Tests for jdk.internal.vm.Continuation.
diff --git a/test/2394-continuation-yields/src/Main.java b/test/2394-continuation-yields/src/Main.java
new file mode 100644
index 0000000..6c0f8b9
--- /dev/null
+++ b/test/2394-continuation-yields/src/Main.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import dalvik.system.VirtualThreadContext;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import jdk.internal.access.SharedSecrets;
+import jdk.internal.vm.Continuation;
+
+/**
+ * Continuation is an internal API in OpenJDK. It verifies that the Continuation run
+ * on different carrier threads before and after yielding.
+ */
+public class Main {
+
+ public static void main(String[] args) throws InterruptedException {
+ if (!com.android.art.flags.Flags.virtualThreadImplV1()) {
+ return;
+ }
+ // Exit if the thread throws any exception.
+ Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
+ System.err.println("thread: " + t.getName());
+ e.printStackTrace(System.err);
+ System.exit(1);
+ });
+
+ testContinuationSleep();
+ }
+
+ private static void testContinuationSleep() throws InterruptedException {
+ Runnable continuationTask = Main::continuationTask;
+ long threadId = Long.MAX_VALUE - 1;
+ VirtualThreadContext virtualThreadContext = new VirtualThreadContext(continuationTask,
+ threadId);
+ Continuation cont = new Continuation(virtualThreadContext);
+
+ Timer timer = new Timer();
+ LinkedBlockingQueue<Object> blockingQueue = new LinkedBlockingQueue<>(1);
+ Runnable carrier2Task = () -> {
+ cont.run();
+ try {
+ blockingQueue.put(new Object());
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ Thread carrier2 = new Thread(carrier2Task);
+ TimerTask sleepTask = new TimerTask() {
+ @Override
+ public void run() {
+ carrier2.start();
+ }
+ };
+
+ Runnable carrier1Task = () -> {
+ cont.run();
+
+ timer.schedule(sleepTask, 100L);
+ };
+ Thread carrier1 = new Thread(carrier1Task);
+ carrier1.start();
+ carrier1.join(1000L);
+
+ blockingQueue.poll(1L, TimeUnit.SECONDS);
+ carrier2.join();
+
+ timer.cancel();
+ }
+
+ private static void continuationTask() {
+ long tid1 = getCarrierThreadId();
+ Continuation.yield(SharedSecrets.getJavaLangAccess().virtualThreadContinuationScope());
+ long tid2 = getCarrierThreadId();
+ if (tid1 == tid2) {
+ throw new RuntimeException("tid shouldn't be the same: "
+ + tid1 + " != " + tid2);
+ }
+ }
+
+ private static long getCarrierThreadId() {
+ return SharedSecrets.getJavaLangAccess().currentCarrierThread().threadId();
+ }
+}
diff --git a/test/2394-continuation-yields/test-metadata.json b/test/2394-continuation-yields/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2394-continuation-yields/test-metadata.json
@@ -0,0 +1,5 @@
+{
+ "build-param": {
+ "jvm-supported": "false"
+ }
+}
diff --git a/test/2395-virtual-thread-reentrantlock/build.py b/test/2395-virtual-thread-reentrantlock/build.py
new file mode 100644
index 0000000..d531de7
--- /dev/null
+++ b/test/2395-virtual-thread-reentrantlock/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2025 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def build(ctx):
+ if ctx.jvm:
+ return # The test does not build on JVM
+ ctx.default_build()
diff --git a/test/2395-virtual-thread-reentrantlock/expected-stderr.txt b/test/2395-virtual-thread-reentrantlock/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2395-virtual-thread-reentrantlock/expected-stderr.txt
diff --git a/test/2395-virtual-thread-reentrantlock/expected-stdout.txt b/test/2395-virtual-thread-reentrantlock/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2395-virtual-thread-reentrantlock/expected-stdout.txt
diff --git a/test/2395-virtual-thread-reentrantlock/info.txt b/test/2395-virtual-thread-reentrantlock/info.txt
new file mode 100644
index 0000000..ae55543
--- /dev/null
+++ b/test/2395-virtual-thread-reentrantlock/info.txt
@@ -0,0 +1 @@
+Tests for Virtual Thread.
diff --git a/test/2395-virtual-thread-reentrantlock/src/Main.java b/test/2395-virtual-thread-reentrantlock/src/Main.java
new file mode 100644
index 0000000..9531cb1
--- /dev/null
+++ b/test/2395-virtual-thread-reentrantlock/src/Main.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantLock;
+
+import jdk.internal.access.SharedSecrets;
+
+/**
+ * Verify that {@link ReentrantLock} API running on Virtual Thread.
+ */
+public class Main {
+
+ public static void main(String[] args) throws InterruptedException, ClassNotFoundException {
+ if (!com.android.art.flags.Flags.virtualThreadImplV1()) {
+ return;
+ }
+ // Exit if the thread throws any exception.
+ Thread.setDefaultUncaughtExceptionHandler(HANDLER);
+
+ verifyCurrentThreadApi();
+ verifyReentrantLock();
+ }
+
+ private static final Thread.UncaughtExceptionHandler HANDLER = (t, e) -> {
+ System.err.println("thread: " + t.getName());
+ e.printStackTrace(System.err);
+ System.exit(1);
+ };
+
+ private static void verifyReentrantLock() throws InterruptedException {
+ ReentrantLock lock = new ReentrantLock();
+ AtomicBoolean v = new AtomicBoolean(false);
+ long timeoutThresholdNs = TimeUnit.SECONDS.toNanos(1);
+ lock.lock();
+ Thread vt = createDefaultVirtualThreadBuilder().start(() -> {
+ lock.lock();
+ v.set(true);
+ lock.unlock();
+ });
+ long startTime = System.nanoTime();
+ while (System.nanoTime() - startTime < timeoutThresholdNs) {
+ if (vt.getVirtualThreadContext().isUnmounted()) {
+ break;
+ }
+ }
+
+ assertEquals(true, vt.getVirtualThreadContext().isUnmounted());
+ assertEquals(false, v.get());
+
+ lock.unlock();
+ vt.join();
+
+ assertEquals(true, v.get());
+ }
+
+
+ static void assertEquals(boolean expected, boolean value) {
+ if (expected == value) {
+ return;
+ }
+ throw new AssertionError("assertEquals expected: " + expected
+ + ", value: " + value);
+ }
+
+ private static void verifyCurrentThreadApi() throws InterruptedException {
+ Thread vt = createDefaultVirtualThreadBuilder().start(() -> {
+ String threadClassName = Thread.currentThread().getClass().getName();
+ if (!("java.lang.VirtualThread".equals(threadClassName))) {
+ throw new AssertionError("Expect a VirtualThread instance");
+ }
+ if (Thread.currentThread().equals(getCarrierThread())) {
+ throw new AssertionError("Thread.currentThread() shouldn't "
+ + "return a carrier thread.");
+ }
+ });
+ vt.join();
+ }
+
+ private static Thread getCarrierThread() {
+ return SharedSecrets.getJavaLangAccess().currentCarrierThread();
+ }
+
+ private static Thread.Builder.OfVirtual createDefaultVirtualThreadBuilder() {
+ return Thread.ofVirtual().uncaughtExceptionHandler(HANDLER);
+ }
+}
diff --git a/test/2395-virtual-thread-reentrantlock/test-metadata.json b/test/2395-virtual-thread-reentrantlock/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2395-virtual-thread-reentrantlock/test-metadata.json
@@ -0,0 +1,5 @@
+{
+ "build-param": {
+ "jvm-supported": "false"
+ }
+}
diff --git a/test/2395-virtual-thread-sleep-api/build.py b/test/2395-virtual-thread-sleep-api/build.py
new file mode 100644
index 0000000..d531de7
--- /dev/null
+++ b/test/2395-virtual-thread-sleep-api/build.py
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2025 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def build(ctx):
+ if ctx.jvm:
+ return # The test does not build on JVM
+ ctx.default_build()
diff --git a/test/2395-virtual-thread-sleep-api/expected-stderr.txt b/test/2395-virtual-thread-sleep-api/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2395-virtual-thread-sleep-api/expected-stderr.txt
diff --git a/test/2395-virtual-thread-sleep-api/expected-stdout.txt b/test/2395-virtual-thread-sleep-api/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2395-virtual-thread-sleep-api/expected-stdout.txt
diff --git a/test/2395-virtual-thread-sleep-api/info.txt b/test/2395-virtual-thread-sleep-api/info.txt
new file mode 100644
index 0000000..b61c3ea
--- /dev/null
+++ b/test/2395-virtual-thread-sleep-api/info.txt
@@ -0,0 +1 @@
+Tests for Thread.sleep() API.
diff --git a/test/2395-virtual-thread-sleep-api/src/Main.java b/test/2395-virtual-thread-sleep-api/src/Main.java
new file mode 100644
index 0000000..5dd44b9
--- /dev/null
+++ b/test/2395-virtual-thread-sleep-api/src/Main.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Verify that {@link Thread#sleep(long)} API parks virtual threads.
+ */
+public class Main {
+
+ public static void main(String[] args) throws InterruptedException, ClassNotFoundException {
+ if (!com.android.art.flags.Flags.virtualThreadImplV1()) {
+ return;
+ }
+ // Exit if the thread throws any exception.
+ Thread.setDefaultUncaughtExceptionHandler(HANDLER);
+
+ testNSleepingThreads(2);
+ testNSleepingThreads(10);
+ testNSleepingThreads(100);
+ testNSleepingThreads(300);
+ }
+
+ private static final Thread.UncaughtExceptionHandler HANDLER = (t, e) -> {
+ System.err.println("thread: " + t.getName());
+ e.printStackTrace(System.err);
+ System.exit(1);
+ };
+
+ private static final int SLEEP_DURATION_MULTIPLIER = 10;
+
+ /**
+ * Start {@code numOfThreads} virtual threads sleeping for the given duration and wait until
+ * all threads join or time out.
+ * @param numOfThreads number of concurrent virtual threads sleeping
+ */
+ private static void testNSleepingThreads(int numOfThreads) throws InterruptedException {
+ long sleepDurationMs = Math.max(numOfThreads * SLEEP_DURATION_MULTIPLIER, 100);
+ long timeoutThresholdNs = TimeUnit.MILLISECONDS.toNanos(
+ sleepDurationMs * 3);
+ List<Thread> threads = new ArrayList<>(numOfThreads);
+ for (int i = 0; i < numOfThreads; i++) {
+ Thread vt = Thread.ofVirtual().uncaughtExceptionHandler(HANDLER).start(() -> {
+ try {
+ Thread.sleep(sleepDurationMs);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ threads.add(vt);
+ }
+
+ List<Thread> threadsToBeUnmounted = new ArrayList<>(threads);
+ long startTime = System.nanoTime();
+ while (System.nanoTime() - startTime < timeoutThresholdNs) {
+ if (threadsToBeUnmounted.isEmpty()) {
+ break;
+ }
+ // We can't assert that a virtual thread runs on different carrier thread before parking
+ // and after un-parking because it is backed by carrier threads from a thread pool.
+ // Instead, we verify that it's unmounted in a busy loop ,
+ threadsToBeUnmounted.removeIf(vt -> vt.getVirtualThreadContext().isUnmounted());
+ }
+
+ if (!threadsToBeUnmounted.isEmpty()) {
+ Thread vt = threadsToBeUnmounted.getFirst();
+ throw new AssertionError("Thread " + vt.threadId() + " wasn't unmounted. "
+ + "Consider increasing SLEEP_DURATION_MULTIPLIER for slow test "
+ + "configurations.");
+ }
+
+ for (Thread vt : threads) {
+ vt.join();
+ }
+ }
+}
diff --git a/test/2395-virtual-thread-sleep-api/test-metadata.json b/test/2395-virtual-thread-sleep-api/test-metadata.json
new file mode 100644
index 0000000..75f6c02
--- /dev/null
+++ b/test/2395-virtual-thread-sleep-api/test-metadata.json
@@ -0,0 +1,5 @@
+{
+ "build-param": {
+ "jvm-supported": "false"
+ }
+}
diff --git a/test/913-heaps/expected-stdout.txt b/test/913-heaps/expected-stdout.txt
index f54fe80..8101819 100644
--- a/test/913-heaps/expected-stdout.txt
+++ b/test/913-heaps/expected-stdout.txt
@@ -1,8 +1,8 @@
---
true true
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=0,location= 31])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780000, length=-1]
1002@0 --(interface)--> 2001@0 [size=123456780004, length=-1]
1002@0 --(superclass)--> 1001@0 [size=123456780001, length=-1]
@@ -46,8 +46,8 @@
root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=10,location= 8])--> 1@1000 [size=16, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=5,location= 20])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780005, length=-1]
1002@0 --(interface)--> 2001@0 [size=123456780009, length=-1]
1002@0 --(superclass)--> 1001@0 [size=123456780006, length=-1]
@@ -86,17 +86,17 @@
5@1002 --(field@9)--> 6@1000 [size=16, length=-1]
6@1000 --(class)--> 1000@0 [size=123456780005, length=-1]
---
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
---
3@1001 --(class)--> 1001@0 [size=123456780011, length=-1]
---
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
---
3@1001 --(class)--> 1001@0 [size=123456780016, length=-1]
---
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=0,location= 31])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
---
1001@0 --(superclass)--> 1000@0 [size=123456780020, length=-1]
3@1001 --(class)--> 1001@0 [size=123456780021, length=-1]
@@ -108,8 +108,8 @@
root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=10,location= 8])--> 1@1000 [size=16, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=5,location= 20])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
---
1001@0 --(superclass)--> 1000@0 [size=123456780025, length=-1]
3@1001 --(class)--> 1001@0 [size=123456780026, length=-1]
@@ -189,8 +189,8 @@
---
---- untagged objects
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=0,location= 31])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780050, length=-1]
1002@0 --(interface)--> 2001@0 [size=123456780054, length=-1]
1002@0 --(superclass)--> 1001@0 [size=123456780051, length=-1]
@@ -234,8 +234,8 @@
root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=10,location= 8])--> 1@1000 [size=16, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=5,location= 20])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780055, length=-1]
1002@0 --(interface)--> 2001@0 [size=123456780059, length=-1]
1002@0 --(superclass)--> 1001@0 [size=123456780056, length=-1]
@@ -275,8 +275,8 @@
6@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
---
---- tagged classes
-root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780060, length=-1]
1002@0 --(interface)--> 2001@0 [size=123456780064, length=-1]
1002@0 --(superclass)--> 1001@0 [size=123456780061, length=-1]
@@ -301,8 +301,8 @@
5@1002 --(field@8)--> 500@0 [size=20, length=2]
6@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
---
-root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=132, length=-1]
-root@root --(thread)--> 3000@0 [size=132, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
+root@root --(thread)--> 3000@0 [size=136, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780065, length=-1]
1002@0 --(interface)--> 2001@0 [size=123456780069, length=-1]
1002@0 --(superclass)--> 1001@0 [size=123456780066, length=-1]
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 4b66f51..4a3db8c 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1628,7 +1628,10 @@
"2390-virtual-thread-parking-error-leak",
"2391-virtual-thread-sleeps",
"2392-virtual-thread-pinning-jni",
- "2393-virtual-thread-pinning-monitor"],
+ "2393-virtual-thread-pinning-monitor",
+ "2394-continuation-yields",
+ "2395-virtual-thread-reentrantlock",
+ "2395-virtual-thread-sleep-api"],
"variant": "jvm",
"description": ["Tests for ART-specific Virtual Thread APIs."]
},