Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
38 changes: 30 additions & 8 deletions src/hotspot/share/prims/jvmti.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10710,6 +10710,15 @@ myInit() {
<eventlink id="VirtualThreadEnd"></eventlink>.
</description>
</capabilityfield>
<capabilityfield id="can_support_value_objects" since="28">
<description>
Can support value objects.
When preview features are enabled and this capability is enabled, then
events are sent for value objects when the following two events are enabled:
<eventlink id="VMObjectAlloc"></eventlink>,
Comment thread
sspitsyn marked this conversation as resolved.
<eventlink id="SampledObjectAlloc"></eventlink>.
</description>
</capabilityfield>
</capabilitiestypedef>

<function id="GetPotentialCapabilities" jkernel="yes" phase="onload" num="140">
Expand Down Expand Up @@ -14013,9 +14022,11 @@ myInit() {
VM. These methods should send this event.
Virtual machines which are incapable of bytecode instrumentation
for some or all of their methods can send this event.
When preview features are enabled and the allocated object has no identity,
<code>null</code> is passed for the <paramlink id="object"></paramlink> parameter.

<p/>
When preview features are enabled and the capability <code>can_support_value_objects</code>
is enabled, this event is sent for the value object allocations.
<code>null</code> is passed for the <paramlink id="object"></paramlink> parameter in such cases.
Comment thread
sspitsyn marked this conversation as resolved.
<p/>
Note that the <internallink
id="SampledObjectAlloc">SampledObjectAlloc</internallink>
event is triggered on all Java object allocations, including those
Expand All @@ -14041,6 +14052,7 @@ myInit() {
<origin>new</origin>
<capabilities>
<required id="can_generate_vm_object_alloc_events"></required>
<capability id="can_support_value_objects"></capability>
</capabilities>
<parameters>
<param id="jni_env">
Expand All @@ -14061,7 +14073,8 @@ myInit() {
<jobject/>
<description>
JNI local reference to the object that was allocated.
Null when preview features are enabled and the allocated object has no identity.
Null when preview features are enabled, the capability <code>can_support_value_objects</code>
is enabled and the allocated object has no identity.
Comment thread
sspitsyn marked this conversation as resolved.
Outdated
</description>
</param>
<param id="object_klass">
Expand Down Expand Up @@ -14108,13 +14121,15 @@ myInit() {
In conjunction with weak references and the function
<functionlink id="GetStackTrace"></functionlink>, a user can track which objects were allocated from which
stack trace, and which are still live during the execution of the program.
When preview features are enabled and the allocated object has no identity,
<code>null</code> is passed for the <paramlink id="object"></paramlink> parameter.
Note that weak references support identity objects only.
<p/>
When preview features are enabled and the capability <code>can_support_value_objects</code>
is enabled, this event is sent for the allocated objects that have no identity.
<code>null</code> is passed for the <paramlink id="object"></paramlink> parameter in such cases.
</description>
<origin>new</origin>
<capabilities>
<required id="can_generate_sampled_object_alloc_events"></required>
<capability id="can_support_value_objects"></capability>
</capabilities>
<parameters>
<param id="jni_env">
Expand All @@ -14135,7 +14150,8 @@ myInit() {
<jobject/>
<description>
JNI local reference to the object that was allocated.
Null when preview features are enabled and the allocated object has no identity.
Null when preview features are enabled, the capability <code>can_support_value_objects</code>
is enabled and the allocated object has no identity.
Comment thread
sspitsyn marked this conversation as resolved.
Outdated
</description>
</param>
<param id="object_klass">
Expand Down Expand Up @@ -15503,6 +15519,12 @@ typedef void (JNICALL *jvmtiEventVMInit)
<change date="10 January 2025" version="25.0.0">
Add new function ClearAllFramePops. Needed to speedup debugger single stepping.
</change>
<change date="30 June 2026" version="28.0.0">
Support for value objects (Preview):
Add new capability: can_support_value_objects which enables
the following events for value objects:
JVMTI_EVENT_VM_OBJECT_ALLOC, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC.
</change>
</changehistory>

</specification>
Expand Down
21 changes: 16 additions & 5 deletions src/hotspot/share/prims/jvmtiExport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,7 @@ bool JvmtiExport::_can_post_frame_pop = fals
bool JvmtiExport::_can_pop_frame = false;
bool JvmtiExport::_can_force_early_return = false;
bool JvmtiExport::_can_support_virtual_threads = false;
bool JvmtiExport::_can_support_value_objects = false;
bool JvmtiExport::_can_get_owned_monitor_info = false;

bool JvmtiExport::_early_vmstart_recorded = false;
Expand Down Expand Up @@ -2967,17 +2968,22 @@ void JvmtiExport::post_vm_object_alloc(JavaThread *thread, oop object) {
if (thread->should_hide_jvmti_events()) {
return;
}
const bool is_inline = object->is_inline();
if (is_inline && !JvmtiExport::can_support_value_objects()) {
return;
}
HandleMark hm(thread);
Handle h(thread, object);

EVT_TRIG_TRACE(JVMTI_EVENT_VM_OBJECT_ALLOC, ("[%s] Trg vm object alloc triggered",
JvmtiTrace::safe_get_thread_name(thread)));
JvmtiEnvIterator it;
for (JvmtiEnv* env = it.first(); env != nullptr; env = it.next(env)) {
if (env->is_enabled(JVMTI_EVENT_VM_OBJECT_ALLOC)) {
if (env->is_enabled(JVMTI_EVENT_VM_OBJECT_ALLOC) &&
(!is_inline || env->get_capabilities()->can_support_value_objects != 0)) {
EVT_TRACE(JVMTI_EVENT_VM_OBJECT_ALLOC, ("[%s] Evt vmobject alloc sent %s",
JvmtiTrace::safe_get_thread_name(thread),
object==nullptr? "null" : object->klass()->external_name()));
Comment thread
sspitsyn marked this conversation as resolved.
object->klass()->external_name()));

JvmtiObjectAllocEventMark jem(thread, h());
JVMTI_JAVA_THREAD_EVENT_CALLBACK_BLOCK(thread)
Expand All @@ -3004,19 +3010,24 @@ void JvmtiExport::post_sampled_object_alloc(JavaThread *thread, oop object) {
if (thread->should_hide_jvmti_events()) {
return;
}
const bool is_inline = object->is_inline();
if (is_inline && !JvmtiExport::can_support_value_objects()) {
return;
}

EVT_TRIG_TRACE(JVMTI_EVENT_SAMPLED_OBJECT_ALLOC,
("[%s] Trg sampled object alloc triggered",
JvmtiTrace::safe_get_thread_name(thread)));
JvmtiEnvThreadStateIterator it(state);
for (JvmtiEnvThreadState* ets = it.first(); ets != nullptr; ets = it.next(ets)) {
if (ets->is_enabled(JVMTI_EVENT_SAMPLED_OBJECT_ALLOC)) {
JvmtiEnv *env = ets->get_env();
Comment thread
sspitsyn marked this conversation as resolved.
if (ets->is_enabled(JVMTI_EVENT_SAMPLED_OBJECT_ALLOC) &&
(!is_inline || env->get_capabilities()->can_support_value_objects != 0)) {
EVT_TRACE(JVMTI_EVENT_SAMPLED_OBJECT_ALLOC,
("[%s] Evt sampled object alloc sent %s",
JvmtiTrace::safe_get_thread_name(thread),
object == nullptr ? "null" : object->klass()->external_name()));
object->klass()->external_name()));

JvmtiEnv *env = ets->get_env();
JvmtiObjectAllocEventMark jem(thread, h());
JVMTI_JAVA_THREAD_EVENT_CALLBACK_BLOCK(thread)
jvmtiEventSampledObjectAlloc callback = env->callbacks()->SampledObjectAlloc;
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/prims/jvmtiExport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class JvmtiExport : public AllStatic {
JVMTI_SUPPORT_FLAG(can_pop_frame)
JVMTI_SUPPORT_FLAG(can_force_early_return)
JVMTI_SUPPORT_FLAG(can_support_virtual_threads)
JVMTI_SUPPORT_FLAG(can_support_value_objects)

JVMTI_SUPPORT_FLAG(early_vmstart_recorded)
JVMTI_SUPPORT_FLAG(can_get_owned_monitor_info) // includes can_get_owned_monitor_stack_depth_info
Expand Down
15 changes: 14 additions & 1 deletion src/hotspot/share/prims/jvmtiManageCapabilities.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 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
Expand Down Expand Up @@ -56,6 +56,7 @@ jvmtiCapabilities JvmtiManageCapabilities::onload_solo_remaining_capabilities;
jvmtiCapabilities JvmtiManageCapabilities::acquired_capabilities;

int JvmtiManageCapabilities::_can_support_virtual_threads_count = 0;
int JvmtiManageCapabilities::_can_support_value_objects_count = 0;

Mutex* JvmtiManageCapabilities::_capabilities_lock = nullptr;

Expand Down Expand Up @@ -103,6 +104,7 @@ jvmtiCapabilities JvmtiManageCapabilities::init_always_capabilities() {
jc.can_generate_resource_exhaustion_heap_events = 1;
jc.can_generate_resource_exhaustion_threads_events = 1;
jc.can_support_virtual_threads = 1;
jc.can_support_value_objects = 1;
return jc;
}

Expand Down Expand Up @@ -277,6 +279,9 @@ jvmtiError JvmtiManageCapabilities::add_capabilities(const jvmtiCapabilities *cu
if (desired->can_support_virtual_threads != 0 && current->can_support_virtual_threads == 0) {
_can_support_virtual_threads_count++;
}
if (desired->can_support_value_objects != 0 && current->can_support_value_objects == 0) {
_can_support_value_objects_count++;
}

// return the result
either(current, desired, result);
Expand Down Expand Up @@ -309,6 +314,11 @@ void JvmtiManageCapabilities::relinquish_capabilities(const jvmtiCapabilities *c
assert(_can_support_virtual_threads_count > 0, "sanity check");
_can_support_virtual_threads_count--;
}
if (to_trash.can_support_value_objects != 0) {
assert(current->can_support_value_objects != 0, "sanity check");
assert(_can_support_value_objects_count > 0, "sanity check");
_can_support_value_objects_count--;
}

update();

Expand Down Expand Up @@ -393,6 +403,7 @@ void JvmtiManageCapabilities::update() {
JvmtiExport::set_can_pop_frame(avail.can_pop_frame);
JvmtiExport::set_can_force_early_return(avail.can_force_early_return);
JvmtiExport::set_can_support_virtual_threads(_can_support_virtual_threads_count != 0);
JvmtiExport::set_can_support_value_objects(_can_support_value_objects_count != 0);
JvmtiExport::set_should_clean_up_heap_objects(avail.can_generate_breakpoint_events);
JvmtiExport::set_can_get_owned_monitor_info(avail.can_get_owned_monitor_info ||
avail.can_get_owned_monitor_stack_depth_info);
Expand Down Expand Up @@ -488,6 +499,8 @@ void JvmtiManageCapabilities:: print(const jvmtiCapabilities* cap) {
log_trace(jvmti)("can_generate_early_class_hook_events");
if (cap->can_support_virtual_threads)
log_trace(jvmti)("can_support_virtual_threads");
if (cap->can_support_value_objects)
log_trace(jvmti)("can_support_value_objects");
}

#endif
5 changes: 4 additions & 1 deletion src/hotspot/share/prims/jvmtiManageCapabilities.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 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
Expand Down Expand Up @@ -51,6 +51,9 @@ class JvmtiManageCapabilities : public AllStatic {
// counter for the agents possess can_support_virtual_threads capability
static int _can_support_virtual_threads_count;

// counter for the agents possess can_support_value_objects capability
static int _can_support_value_objects_count;

// lock to access the class data
static Mutex* _capabilities_lock;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,6 @@ static void event_storage_add(EventStorage* storage,
jint count;
jvmtiError err;

if (!jni->HasIdentity(object)) {
Comment thread
sspitsyn marked this conversation as resolved.
// weak references are prohibited for non-identity objects, skip them
return;
}

err = jvmti->GetStackTrace(thread, 0, 64, frames, &count);
if (err == JVMTI_ERROR_NONE && count >= 1) {
ObjectTrace* live_object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
*
* @enablePreview
* @run main/othervm/native -agentlib:SampledObjectAllocValue SampledObjectAllocValue
* @run main/othervm/native -agentlib:SampledObjectAllocValue=can_support_value_objects SampledObjectAllocValue
*/

public class SampledObjectAllocValue {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* questions.
*/

#include <string.h>
#include "jvmti.h"
#include "jvmti_common.hpp"

Expand All @@ -31,6 +32,7 @@ extern "C" {
static std::atomic<int> events_counter(0);
static jvmtiEnv* jvmti;
static jclass expected_class;
static bool expect_events = false;

static bool
is_test_class(jvmtiEnv* jvmti, JNIEnv* jni, jclass cls) {
Expand Down Expand Up @@ -78,13 +80,18 @@ VMDeath(jvmtiEnv* jvmti, JNIEnv* jni) {
jvmtiError err = jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, nullptr);
check_jvmti_error(err, "SetEventNotificationMode for SAMPLED_OBJECT_ALLOC");

if (events_counter == 0) {
LOG("VMDeath: expect_events: %d events_counter: %d\n", expect_events, events_counter.load());

if (expect_events && events_counter == 0) {
fatal(jni, "SampledObjectAlloc events counter shouldn't be zero");
}
LOG("VMDeath: events_counter: %d\n", events_counter.load());
if (!expect_events && events_counter != 0) {
fatal(jni, "SampledObjectAlloc events counter should be zero");
}
}

jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
bool support_value_objects = options != nullptr && strcmp(options, "can_support_value_objects") == 0;
jvmtiCapabilities caps;
jvmtiEventCallbacks callbacks;
jvmtiError err;
Expand All @@ -99,6 +106,11 @@ jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {

memset(&caps, 0, sizeof(caps));
caps.can_generate_sampled_object_alloc_events = 1;
if (support_value_objects) {
caps.can_support_value_objects = 1;
expect_events = true;
}

if (jvmti->AddCapabilities(&caps) != JVMTI_ERROR_NONE) {
return JNI_ERR;
}
Expand All @@ -110,7 +122,7 @@ jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) {
err = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
check_jvmti_error(err, "SetEventCallbacks");

// Interval should be small enough to triggger sampling event.
// Interval should be small enough to trigger sampling event.
err = jvmti->SetHeapSamplingInterval(100);
check_jvmti_error(err, "SetHeapSamplingInterval");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
* @summary Verifies that a VMObjectAlloc event is generated for a value object created using MethodHandle
* @requires vm.jvmti
* @enablePreview
* @run main/othervm/native -agentlib:VMObjectAllocValueTest VMObjectAllocValueTest
* @run main/othervm/native -agentlib:VMObjectAllocValueTest VMObjectAllocValueTest 0
* @run main/othervm/native -agentlib:VMObjectAllocValueTest=can_support_value_objects VMObjectAllocValueTest 1
*/

import java.lang.invoke.MethodHandle;
Expand All @@ -47,8 +48,11 @@ public static void main(String[] args) throws Throwable {
MethodHandle mh = publicLookup.findConstructor(VMObjectAllocValueTest.class, mt);
mh.invoke("str"); // to trigger a VMObjectAlloc event invoke ctor not from Java

if (getNumberOfAllocation() != 1) {
throw new Exception("Number of allocation != 1");
int expected = Integer.parseInt(args[0]);
int actual = getNumberOfAllocation();

if (actual != expected) {
throw new Exception("Number of allocation: expected: " + expected + ", got: " + actual);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Java_VMObjectAllocValueTest_getNumberOfAllocation(JNIEnv *env, jclass cls) {

extern JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
bool support_value_objects = options != nullptr && strcmp(options, "can_support_value_objects") == 0;
jvmtiEventCallbacks callbacks;
jvmtiError err;
jvmtiCapabilities caps;
Expand All @@ -90,6 +91,9 @@ Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
callbacks.VMDeath = &VMDeath;
memset(&caps, 0, sizeof(caps));
caps.can_generate_vm_object_alloc_events = 1;
if (support_value_objects) {
caps.can_support_value_objects = 1;
}

err = jvmti->AddCapabilities( &caps);
if (err != JVMTI_ERROR_NONE) {
Expand Down