From 52b328e0947537185e57a4511ee443f8e8570fe8 Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Sun, 5 Apr 2026 17:26:28 +0900 Subject: [PATCH 1/2] Add object monitor to Jvm with object_wait, object_listen and object_notify --- Cargo.lock | 2 +- java_runtime/Cargo.toml | 1 - jvm/Cargo.toml | 2 ++ jvm/src/jvm.rs | 36 +++++++++++++++++++++++++++++++++++- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b0f60c9..fd625c2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,7 +363,6 @@ dependencies = [ "chrono", "dyn-clone", "encoding_rs", - "event-listener", "hashbrown", "java_class_proto", "java_constants", @@ -387,6 +386,7 @@ dependencies = [ "bytemuck", "dyn-clone", "dyn-hash", + "event-listener", "hashbrown", "java_constants", "nom", diff --git a/java_runtime/Cargo.toml b/java_runtime/Cargo.toml index 2a6af69e..f3f4d9a8 100644 --- a/java_runtime/Cargo.toml +++ b/java_runtime/Cargo.toml @@ -14,7 +14,6 @@ tracing = { workspace = true } chrono = { version = "^0.4", default-features = false } encoding_rs = { version = "^0.8", features = ["alloc"], default-features = false } -event-listener = { version = "^5.4", default-features = false } zip = { version = "^6.0", features = ["deflate"], default-features = false } url = { version = "^2.5", default-features = false } diff --git a/jvm/Cargo.toml b/jvm/Cargo.toml index 92b24a36..55762af0 100644 --- a/jvm/Cargo.toml +++ b/jvm/Cargo.toml @@ -16,6 +16,8 @@ nom = { workspace = true } parking_lot = { workspace = true } tracing = { workspace = true } +event-listener = { version = "^5.4", default-features = false } + java_constants = { workspace = true } [dev-dependencies] diff --git a/jvm/src/jvm.rs b/jvm/src/jvm.rs index 4cea8cc0..b80c283e 100644 --- a/jvm/src/jvm.rs +++ b/jvm/src/jvm.rs @@ -3,6 +3,7 @@ use alloc::{borrow::ToOwned, boxed::Box, collections::BTreeMap, format, string::String, sync::Arc, vec::Vec}; use core::{ fmt::Debug, + hash::BuildHasher, iter, mem::{forget, size_of_val}, sync::atomic::{AtomicBool, Ordering}, @@ -10,7 +11,8 @@ use core::{ use bytemuck::cast_slice; use dyn_clone::clone_box; -use hashbrown::HashSet; +use event_listener::{Event, EventListener}; +use hashbrown::{DefaultHashBuilder, HashSet}; use parking_lot::RwLock; use java_constants::{ClassAccessFlags, MethodAccessFlags}; @@ -36,6 +38,8 @@ struct JvmInner { classes: RwLock>, threads: RwLock>, all_objects: RwLock>>, + monitors: RwLock>>, + monitor_hasher: DefaultHashBuilder, get_current_thread_id: Box u64 + Sync + Send>, bootstrap_class_loader: Box, bootstrapping: AtomicBool, @@ -57,6 +61,8 @@ impl Jvm { classes: RwLock::new(BTreeMap::new()), threads: RwLock::new(BTreeMap::new()), all_objects: RwLock::new(HashSet::new()), + monitors: RwLock::new(BTreeMap::new()), + monitor_hasher: DefaultHashBuilder::default(), get_current_thread_id: Box::new(get_current_thread_id), bootstrap_class_loader: Box::new(bootstrap_class_loader), bootstrapping: AtomicBool::new(true), @@ -464,6 +470,21 @@ impl Jvm { self.inner.classes.read().get(class_name).cloned() } + pub fn object_listen(&self, obj: &Box) -> EventListener { + self.get_or_create_monitor(obj).listen() + } + + pub async fn object_wait(&self, obj: &Box) -> Result<()> { + self.object_listen(obj).await; + + Ok(()) + } + + pub fn object_notify(&self, obj: &Box, count: usize) { + let monitor = self.get_or_create_monitor(obj); + monitor.notify(count); + } + #[async_recursion::async_recursion] pub async fn resolve_class(&self, class_name: &str) -> Result { self.resolve_class_internal(class_name, None).await @@ -720,6 +741,19 @@ impl Jvm { } } + fn get_or_create_monitor(&self, obj: &Box) -> Arc { + let key = self.inner.monitor_hasher.hash_one(obj); + + let monitors = self.inner.monitors.read(); + if let Some(monitor) = monitors.get(&key) { + return monitor.clone(); + } + drop(monitors); + + let mut monitors = self.inner.monitors.write(); + monitors.entry(key).or_insert_with(|| Arc::new(Event::new())).clone() + } + pub(crate) fn find_field(&self, class: &dyn ClassDefinition, name: &str, descriptor: &str) -> Result>> { let field = class.field(name, descriptor, false); From 70c6193b9b96a485a33c4b146d87d66918aaeeb9 Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Sun, 5 Apr 2026 17:26:35 +0900 Subject: [PATCH 2/2] Replace rust_object_field events with Jvm monitor in Object and Thread --- java_runtime/src/classes/java/lang/object.rs | 47 +++++++------------- java_runtime/src/classes/java/lang/thread.rs | 45 ++++++++----------- 2 files changed, 35 insertions(+), 57 deletions(-) diff --git a/java_runtime/src/classes/java/lang/object.rs b/java_runtime/src/classes/java/lang/object.rs index ab435a28..b93c953d 100644 --- a/java_runtime/src/classes/java/lang/object.rs +++ b/java_runtime/src/classes/java/lang/object.rs @@ -1,13 +1,12 @@ use core::{hash::BuildHasher, time::Duration}; -use alloc::{boxed::Box, format, sync::Arc, vec}; +use alloc::{boxed::Box, format, vec}; use dyn_clone::clone_box; -use event_listener::Event; use hashbrown::DefaultHashBuilder; -use java_class_proto::{JavaFieldProto, JavaMethodProto}; +use java_class_proto::JavaMethodProto; use java_constants::MethodAccessFlags; -use jvm::{Array, ClassInstance, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use jvm::{ClassInstance, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{Runtime, RuntimeClassProto, RuntimeContext, SpawnCallback, classes::java::lang::String}; @@ -34,7 +33,7 @@ impl Object { JavaMethodProto::new("wait", "()V", Self::wait, Default::default()), JavaMethodProto::new("finalize", "()V", Self::finalize, Default::default()), ], - fields: vec![JavaFieldProto::new("waitEvent", "[B", Default::default())], + fields: vec![], access_flags: Default::default(), } } @@ -107,13 +106,7 @@ impl Object { async fn notify(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.lang.Object::notify({:?})", &this); - let wait_event: ClassInstanceRef> = jvm.get_field(&this, "waitEvent", "[B").await?; - if wait_event.is_null() { - return Ok(()); - } - - let wait_event: Arc = jvm.get_rust_object_field(&this, "waitEvent").await?; - wait_event.notify(1); + jvm.object_notify(&this, 1); Ok(()) } @@ -121,13 +114,7 @@ impl Object { async fn notify_all(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.lang.Object::notifyAll({:?})", &this); - let wait_event: ClassInstanceRef> = jvm.get_field(&this, "waitEvent", "[B").await?; - if wait_event.is_null() { - return Ok(()); - } - - let wait_event: Arc = jvm.get_rust_object_field(&this, "waitEvent").await?; - wait_event.notify(usize::MAX); + jvm.object_notify(&this, usize::MAX); Ok(()) } @@ -140,24 +127,21 @@ impl Object { Ok(()) } - async fn wait_long_int(jvm: &Jvm, context: &mut RuntimeContext, mut this: ClassInstanceRef, millis: i64, nanos: i32) -> Result<()> { + async fn wait_long_int(jvm: &Jvm, context: &mut RuntimeContext, this: ClassInstanceRef, millis: i64, nanos: i32) -> Result<()> { tracing::debug!("java.lang.Object::wait({:?}, {:?}, {:?})", &this, millis, nanos); - let wait_event = Arc::new(Event::new()); - jvm.put_rust_object_field(&mut this, "waitEvent", wait_event.clone()).await?; - - struct Waiter { + struct TimeoutNotifier { timeout: i64, - wait_event: Arc, + jvm: Jvm, + this: Box, context: Box, } #[async_trait::async_trait] - impl SpawnCallback for Waiter { + impl SpawnCallback for TimeoutNotifier { async fn call(&self) -> Result<()> { self.context.sleep(Duration::from_millis(self.timeout as _)).await; - - self.wait_event.notify(1); // TODO this would notify other waiter + self.jvm.object_notify(&self.this, 1); // TODO this may wake an unrelated waiter Ok(()) } } @@ -166,15 +150,16 @@ impl Object { if timeout != 0 { context.spawn( jvm, - Box::new(Waiter { + Box::new(TimeoutNotifier { timeout, - wait_event: wait_event.clone(), + jvm: jvm.clone(), + this: this.clone().into(), context: clone_box(context), }), ); } - wait_event.listen().await; + jvm.object_wait(&this).await?; Ok(()) } diff --git a/java_runtime/src/classes/java/lang/thread.rs b/java_runtime/src/classes/java/lang/thread.rs index d0e710d8..c96eeccc 100644 --- a/java_runtime/src/classes/java/lang/thread.rs +++ b/java_runtime/src/classes/java/lang/thread.rs @@ -1,11 +1,9 @@ -use alloc::{boxed::Box, sync::Arc, vec}; +use alloc::{boxed::Box, vec}; use core::time::Duration; -use event_listener::Event; - use java_class_proto::{JavaFieldProto, JavaMethodProto}; use java_constants::MethodAccessFlags; -use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; +use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString}; use crate::{RuntimeClassProto, RuntimeContext, SpawnCallback, classes::java::lang::Runnable}; @@ -40,7 +38,7 @@ impl Thread { fields: vec![ JavaFieldProto::new("id", "J", Default::default()), JavaFieldProto::new("target", "Ljava/lang/Runnable;", Default::default()), - JavaFieldProto::new("joinEvent", "[B", Default::default()), + JavaFieldProto::new("alive", "Z", Default::default()), ], access_flags: Default::default(), } @@ -84,7 +82,6 @@ impl Thread { struct ThreadStartProxy { jvm: Jvm, thread_id: i32, - join_event: Arc, this: ClassInstanceRef, } @@ -129,14 +126,15 @@ impl Thread { self.jvm.detach_thread()?; - self.join_event.notify(usize::MAX); + let mut this = self.this.clone(); + self.jvm.put_field(&mut this, "alive", "Z", false).await.unwrap(); + self.jvm.object_notify(&self.this, usize::MAX); Ok(()) } } - let join_event = Arc::new(Event::new()); - jvm.put_rust_object_field(&mut this, "joinEvent", join_event.clone()).await?; + jvm.put_field(&mut this, "alive", "Z", true).await?; let id: i32 = jvm.invoke_virtual(&this, "hashCode", "()I", ()).await?; @@ -145,7 +143,6 @@ impl Thread { Box::new(ThreadStartProxy { jvm: jvm.clone(), thread_id: id, - join_event, this: this.clone(), }), ); @@ -164,27 +161,23 @@ impl Thread { Ok(()) } - async fn join(jvm: &Jvm, _context: &mut RuntimeContext, mut this: ClassInstanceRef) -> Result<()> { + async fn join(jvm: &Jvm, _context: &mut RuntimeContext, this: ClassInstanceRef) -> Result<()> { tracing::debug!("java.lang.Thread::join({:?})", &this); - // TODO we don't have get same field twice - let raw_join_event: ClassInstanceRef> = jvm.get_field(&this, "joinEvent", "[B").await?; - if raw_join_event.is_null() { - return Ok(()); // already joined or not started + loop { + let listener = jvm.object_listen(&this); + let alive: bool = jvm.get_field(&this, "alive", "Z").await?; + if !alive { + return Ok(()); + } + listener.await; } - - let join_event: Arc = jvm.get_rust_object_field(&this, "joinEvent").await?; - join_event.listen().await; - - jvm.put_field(&mut this, "joinEvent", "[B", None).await?; - - Ok(()) } - async fn is_alive(_jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { - tracing::warn!("stub java.lang.Thread::isAlive({:?})", &this); - - Ok(true) + async fn is_alive(jvm: &Jvm, _: &mut RuntimeContext, this: ClassInstanceRef) -> Result { + tracing::debug!("java.lang.Thread::isAlive({:?})", &this); + let alive: bool = jvm.get_field(&this, "alive", "Z").await?; + Ok(alive) } async fn sleep(_: &Jvm, context: &mut RuntimeContext, duration: i64) -> Result<()> {