Skip to content

Commit f972710

Browse files
committed
Implement garbage collection
1 parent 8821b95 commit f972710

File tree

16 files changed

+370
-22
lines changed

16 files changed

+370
-22
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

java_runtime/src/classes/java/lang/class.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ mod test {
9999
async fn test_class() -> Result<()> {
100100
let jvm = test_jvm().await?;
101101

102-
let java_class = jvm.resolve_class("java/lang/String").await?.java_class()?;
102+
let java_class = jvm.resolve_class("java/lang/String").await?.java_class();
103103

104104
let rust_class = JavaLangClass::to_rust_class(&jvm, &java_class).await?;
105105
assert_eq!(rust_class.name(), "java/lang/String");
@@ -115,15 +115,15 @@ mod test {
115115
async fn test_is_assignable_from() -> Result<()> {
116116
let jvm = test_jvm().await?;
117117

118-
let string_class = jvm.resolve_class("java/lang/String").await?.java_class()?;
119-
let object_class = jvm.resolve_class("java/lang/Object").await?.java_class()?;
118+
let string_class = jvm.resolve_class("java/lang/String").await?.java_class();
119+
let object_class = jvm.resolve_class("java/lang/Object").await?.java_class();
120120

121121
let result: bool = jvm
122122
.invoke_virtual(&object_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (string_class.clone(),))
123123
.await?;
124124
assert!(result);
125125

126-
let thread_class = jvm.resolve_class("java/lang/Thread").await?.java_class()?;
126+
let thread_class = jvm.resolve_class("java/lang/Thread").await?.java_class();
127127

128128
let result: bool = jvm
129129
.invoke_virtual(&string_class, "isAssignableFrom", "(Ljava/lang/Class;)Z", (thread_class,))

java_runtime/src/classes/java/lang/class_loader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ impl ClassLoader {
207207

208208
let class = jvm.resolve_class(&rust_name).await?;
209209

210-
Ok(class.java_class()?.into())
210+
Ok(class.java_class().into())
211211
}
212212

213213
async fn get_resource(

java_runtime/src/classes/java/lang/object.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl Object {
5151
let this: Box<dyn ClassInstance> = this.into();
5252
let class_name = this.class_definition().name();
5353

54-
let class = jvm.resolve_class(&class_name).await?.java_class()?;
54+
let class = jvm.resolve_class(&class_name).await?.java_class();
5555

5656
Ok(class.into())
5757
}

jvm/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ parking_lot = { workspace = true }
1717
tracing = { workspace = true }
1818

1919
java_constants = { workspace = true }
20+
21+
[dev-dependencies]
22+
tokio = { workspace = true, features = ["rt-multi-thread", "time"] }
23+
24+
jvm_rust = { workspace = true }
25+
java_runtime = { workspace = true }

jvm/src/array_class_definition.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use alloc::{
22
boxed::Box,
33
format,
44
string::{String, ToString},
5+
vec::Vec,
56
};
67
use dyn_clone::clone_trait_object;
78

@@ -36,6 +37,10 @@ impl<T: ArrayClassDefinition> ClassDefinition for T {
3637
None
3738
}
3839

40+
fn fields(&self) -> Vec<Box<dyn Field>> {
41+
Vec::new()
42+
}
43+
3944
fn get_static_field(&self, _field: &dyn Field) -> Result<JavaValue> {
4045
panic!("Array classes do not have static fields")
4146
}

jvm/src/class_definition.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use alloc::{boxed::Box, string::String};
1+
use alloc::{boxed::Box, string::String, vec::Vec};
22
use core::fmt::Debug;
33

44
use dyn_clone::{clone_trait_object, DynClone};
@@ -12,6 +12,7 @@ pub trait ClassDefinition: Sync + Send + AsAny + Debug + DynClone {
1212
fn instantiate(&self) -> Result<Box<dyn ClassInstance>>;
1313
fn method(&self, name: &str, descriptor: &str, is_static: bool) -> Option<Box<dyn Method>>;
1414
fn field(&self, name: &str, descriptor: &str, is_static: bool) -> Option<Box<dyn Field>>;
15+
fn fields(&self) -> Vec<Box<dyn Field>>;
1516
fn get_static_field(&self, field: &dyn Field) -> Result<JavaValue>; // TODO do we need to split class? or rename classdefinition?
1617
fn put_static_field(&mut self, field: &dyn Field, value: JavaValue) -> Result<()>;
1718
fn as_array_class_definition(&self) -> Option<&dyn ArrayClassDefinition> {

jvm/src/class_loader.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ impl Class {
2525
*self.java_class.write() = Some(java_class);
2626
}
2727

28-
pub fn java_class(&self) -> Result<Box<dyn ClassInstance>> {
29-
Ok(self.java_class.read().clone().unwrap())
28+
pub fn java_class(&self) -> Box<dyn ClassInstance> {
29+
self.java_class.read().clone().unwrap()
3030
}
3131
}
3232

jvm/src/garbage_collector.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec};
2+
use core::mem::forget;
3+
4+
use bytemuck::cast_slice;
5+
use hashbrown::{hash_set::Entry, HashMap, HashSet};
6+
use parking_lot::Mutex;
7+
8+
use crate::{thread::JvmThread, ClassInstance, JavaType, JavaValue, Jvm};
9+
10+
// XXX java/util/Vector, java/util/HashMap internal..
11+
type RustVector = Arc<Mutex<Vec<Box<dyn ClassInstance>>>>;
12+
type RustHashMap = Arc<Mutex<HashMap<i32, Vec<(Box<dyn ClassInstance>, Box<dyn ClassInstance>)>>>>;
13+
14+
pub fn determine_garbage(
15+
jvm: &Jvm,
16+
threads: &BTreeMap<u64, JvmThread>,
17+
all_class_instances: &HashSet<Box<dyn ClassInstance>>,
18+
classes: Vec<Box<dyn ClassInstance>>,
19+
) -> Vec<Box<dyn ClassInstance>> {
20+
let mut reachable_objects = classes.into_iter().collect::<HashSet<_>>();
21+
22+
threads
23+
.iter()
24+
.flat_map(|(_, thread)| thread.iter_frame().flat_map(|stack| stack.local_variables()))
25+
.for_each(|x| {
26+
find_reachable_objects(jvm, x, &mut reachable_objects);
27+
});
28+
29+
// HACK we should test if class loader is in use
30+
for class_instance in all_class_instances.iter() {
31+
if jvm.is_instance(&**class_instance, "java/lang/ClassLoader") {
32+
find_reachable_objects(jvm, class_instance, &mut reachable_objects);
33+
}
34+
}
35+
36+
all_class_instances.difference(&reachable_objects).cloned().collect()
37+
}
38+
39+
#[allow(clippy::borrowed_box)]
40+
fn find_reachable_objects(jvm: &Jvm, object: &Box<dyn ClassInstance>, reachable_objects: &mut HashSet<Box<dyn ClassInstance>>) {
41+
let entry = reachable_objects.entry(object.clone());
42+
if let Entry::Occupied(_) = entry {
43+
return;
44+
}
45+
entry.insert();
46+
47+
let fields = object.class_definition().fields();
48+
49+
for field in fields {
50+
match field.r#type() {
51+
JavaType::Class(_) => {
52+
let value = object.get_field(&*field).unwrap();
53+
if let JavaValue::Object(Some(value)) = value {
54+
find_reachable_objects(jvm, &value, reachable_objects);
55+
56+
// XXX we have to deal with java value wrapped inside rust type e.g. java.util.Vector, java.util.Hashtable
57+
if jvm.is_instance(&*value, "java/util/Vector") {
58+
let members = vector_members(&*value);
59+
assert!(members.len() == 1);
60+
for member in members {
61+
find_reachable_objects(jvm, &member, reachable_objects);
62+
}
63+
} else if jvm.is_instance(&*value, "java/util/Hashtable") {
64+
let members = hashtable_members(&*value);
65+
for member in members {
66+
find_reachable_objects(jvm, &member, reachable_objects);
67+
}
68+
}
69+
}
70+
}
71+
JavaType::Array(x) => {
72+
if matches!(*x, JavaType::Class(_)) {
73+
let value = object.get_field(&*field).unwrap();
74+
if let JavaValue::Object(Some(value)) = value {
75+
let array = value.as_array_instance().unwrap();
76+
let values = array.load(0, array.length()).unwrap();
77+
78+
for value in values {
79+
if let JavaValue::Object(Some(value)) = value {
80+
find_reachable_objects(jvm, &value, reachable_objects);
81+
}
82+
}
83+
}
84+
}
85+
}
86+
_ => {}
87+
}
88+
}
89+
}
90+
91+
// Same as Jvm's one but without async
92+
fn get_rust_object_field<T: Clone>(object: &dyn ClassInstance, field_name: &str) -> T {
93+
let field = object.class_definition().field(field_name, "Ljava/lang/Object;", true).unwrap();
94+
let value = object.get_field(&*field).unwrap();
95+
let buf: Vec<i8> = match value {
96+
JavaValue::Object(Some(value)) => {
97+
let value_array = value.as_array_instance().unwrap();
98+
99+
value_array.load(0, value_array.length()).unwrap().into_iter().map(|x| x.into()).collect()
100+
}
101+
_ => panic!("Invalid field type"),
102+
};
103+
104+
let rust_raw = usize::from_le_bytes(cast_slice(&buf).try_into().unwrap());
105+
106+
let rust = unsafe { Box::from_raw(rust_raw as *mut T) };
107+
let result = (*rust).clone();
108+
109+
forget(rust); // do not drop box as we still have it in java memory
110+
111+
result
112+
}
113+
114+
fn vector_members(vector: &dyn ClassInstance) -> Vec<Box<dyn ClassInstance>> {
115+
let rust_vector: RustVector = get_rust_object_field(vector, "raw");
116+
117+
let rust_vector = rust_vector.lock();
118+
rust_vector.iter().cloned().collect()
119+
}
120+
121+
fn hashtable_members(hashtable: &dyn ClassInstance) -> Vec<Box<dyn ClassInstance>> {
122+
let rust_hashmap: RustHashMap = get_rust_object_field(hashtable, "raw");
123+
124+
let rust_hashmap = rust_hashmap.lock();
125+
rust_hashmap.iter().flat_map(|(_, v)| v.iter().map(|x| x.1.clone())).collect()
126+
}

jvm/src/jvm.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::{
2222
class_loader::{BootstrapClassLoader, BootstrapClassLoaderWrapper, Class, ClassLoaderWrapper, JavaClassLoaderWrapper},
2323
error::JavaError,
2424
field::Field,
25+
garbage_collector::determine_garbage,
2526
invoke_arg::InvokeArg,
2627
method::Method,
2728
r#type::JavaType,
@@ -115,7 +116,7 @@ impl Jvm {
115116
let mut threads = self.inner.threads.write();
116117
let thread = threads.get_mut(&thread_id).unwrap();
117118

118-
thread.top_frame_mut().local_variables().push(instance.clone());
119+
thread.top_frame_mut().local_variables_mut().push(instance.clone());
119120
self.inner.all_objects.write().insert(instance.clone());
120121

121122
Ok(instance)
@@ -146,7 +147,7 @@ impl Jvm {
146147
let mut threads = self.inner.threads.write();
147148
let thread = threads.get_mut(&thread_id).unwrap();
148149

149-
thread.top_frame_mut().local_variables().push(instance.clone());
150+
thread.top_frame_mut().local_variables_mut().push(instance.clone());
150151
self.inner.all_objects.write().insert(instance.clone());
151152

152153
Ok(instance)
@@ -585,6 +586,31 @@ impl Jvm {
585586
.collect()
586587
}
587588

589+
pub fn collect_garbage(&self) -> Result<usize> {
590+
tracing::trace!("Collecting garbage");
591+
592+
let garbage = {
593+
let threads = self.inner.threads.read();
594+
let all_objects = self.inner.all_objects.read();
595+
let classes = self.inner.classes.read().values().map(|x| x.java_class()).collect();
596+
597+
determine_garbage(self, &threads, &all_objects, classes)
598+
};
599+
600+
let garbage_count = garbage.len();
601+
602+
tracing::trace!("Garbage count: {}", garbage_count);
603+
604+
for object in garbage {
605+
let name = object.class_definition().name();
606+
tracing::trace!("Destroying {:?}({})", object, name);
607+
608+
self.destroy(object).unwrap();
609+
}
610+
611+
Ok(garbage_count)
612+
}
613+
588614
async fn register_class_internal(&self, class: Class, class_loader_wrapper: Option<&dyn ClassLoaderWrapper>) -> Result<()> {
589615
if !class.definition.name().starts_with('[') {
590616
if let Some(super_class) = class.definition.super_class_name() {
@@ -675,7 +701,7 @@ impl Jvm {
675701
return Ok(class_instance.unwrap());
676702
}
677703

678-
let calling_class_class_loader = JavaLangClass::class_loader(self, &class.java_class()?).await?;
704+
let calling_class_class_loader = JavaLangClass::class_loader(self, &class.java_class()).await?;
679705
if let Some(x) = calling_class_class_loader {
680706
Ok(x)
681707
} else {
@@ -744,11 +770,3 @@ impl Jvm {
744770
result
745771
}
746772
}
747-
748-
#[cfg(test)]
749-
mod test {
750-
#[test]
751-
fn test_is_instance() {
752-
// TODO
753-
}
754-
}

0 commit comments

Comments
 (0)