void gc_init() {
gc.init();
}
The information on these pages may be out of date, or may refer to
resources that have moved or have been made read-only.
For more information please refer to the
Apache Attic
This document provides instructions on creating a custom garbage collector implementation (GC, version 1.0, 2006-07-20) in C++ and configuring the DRL virtual machine to use it. The document describes the major steps of this procedure, namely: establishing the build infrastructure, implementing the GC interface, implementing the GC algorithm, and running the VM with the custom GC.
Note
Plugging-in a
user-designed garbage collector
presupposes an operating DRL virtual
machine built according to the
instructions of the
README.txt
filesupplied
with the VM source package.
At this stage, you create the directory and set up the build infrastructure to build the dynamic library. At the end of this stage, you will be fully set for adding the garbage collector code and building it.
DRLVM can load a custom garbage collector from a dynamic library. It is recommended that you build your dynamic library using a DRLVM build infrastructure. Below is an example of creating of a build descriptor on the Windows/IA-32 architecture.
The example below shows how to create a directory for a new GC module:
vm$ mkdir gc_copying vm$ mkdir gc_copying/src vm$ cd gc_copying/src
In the newly created directory you will store the source code. For more information, refer to the Implementing the GC Algorithm section.
Create the build descriptor file
build/make/components/vm/gc_copying.xml
with the following content:
<project name="vm.gc_copying"> <target name="init" depends="common_vm"> <property name="build.depends" value="extra.apr,vm.vmcore" /> <property name="outtype" value="shared" /> <property name="libname" value="gc_copying" /> <property name="src" location="${build.vm.home}" /> <compiler id="cpp.compiler" extends="common.cpp.compiler"> <includepath> <pathelement location="${extra.apr.includes}" /> </includepath> <includepath> <dirset dir="${build.vm.home}"> <include name="include" /> </dirset> </includepath> <fileset dir="${src}/gc_copying/src"> <include name="*.cpp" /> </fileset> <defineset define="BUILDING_GC" /> <select os="win" cfg="release" cxx="icl"> <compilerarg value="/Qip" /> </select> </compiler> <linker id="linker" extends="common.linker"> <libset libs="${vm.vmcore.lib}" dir="${vm.vmcore.libdir}" /> <select os="lnx"> <linkerarg value="-Bsymbolic" /> </select> </linker> </target> </project>
You can add other macro definitions, include directories or compiler-specific command-line options to match your needs.
Create a C++ file with essential include files, namely:
#include "open/gc.h" #include "open/vm_gc.h" #include "open/vm.h" #define LOG_DOMAIN "gc" #include "cxxlog.h"
These include files are located in
directories
vm/include/open
and
vm/port/include.
Consult
their content for documentation and
details of the interface.
Run the build system to test whether the infrastructure is set up correctly:
build$ build.bat -DCOMPONENTS=vm.gc_copying
On a successful build, the
.dll
file is placed to the
VM build directory
build/win_ia32_icl_debug/deploy/jre/bin/.
The name of the directory may differ
depending on your system and the
compiler used.
Note
This empty library will not work, you have to write your GC first.
This section lists the functions
that a garbage collector interface must
implement. Declarations of these
functions are in gc.h
. For
details, consult the
Developer's
Guide and documentation in
gc.h
and
vm_gc.h
.
gc_init()
initializes the garbage
collectorgc_wrapup()
shuts
down the GCgc_vm_initialized()
notifies the GC about the VM
transition from the initialization
stage to running user
applicationsgc_thread_init()
and gc_thread_kill()
notify the GC about creation and
termination of user threads that
may request memory allocation or
other GC servicesgc_class_prepared()
notifies the GC about loaded and
prepared classesgc_alloc()
performs slow allocation, can
trigger garbage collectiongc_alloc_fast()
performs faster allocation, should
not trigger garbage collectiongc_add_root_set_entry()
is responsible for enumerationgc_add_root_set_entry()
enumerates one root pointerSee the Root Set Enumeration section in the Developer's Guide for details.
gc_supports_compressed_references()
indicates whether GC supports
compressed referencesgc_is_object_pinned()
indicates whether the GC will move
an object or notgc_force_gc()
forces a garbage collectionThe virtual machine can operate without the functions listed below, but certain features will be unavailable.
gc_free_memory()
returns the estimated amount of
memory available for
allocationgc_pin_object()
requests that the GC does not move
an objectgc_unpin_object()
removes the restriction on not
moving an objectgc_get_next_live_object()
iterates over live objects during
the stop-the-world phase of garbage
collectiongc_finalize_on_exit()
transfers finalizable queue
contents to the VM core on
shutdowngc_time_since_last_gc()
returns the amount of time that
elapsed since the previous
collection, in millisecondsgc_total_memory()
returns the overall amount of
memory used for the Java heapgc_max_memory()
returns the maximum amount of
memory that can be used for the
Java heapVM_GC
InterfaceThe garbage collector requires VM
support in its operation. The virtual
machine exports the VM_GC
interface to meet the needs of the
garbage collector. Besides, the GC uses
the VM_common
interface.
The VM_GC
interface
describes the services that the VM
provides specifically for the garbage
collector. Please refer to the header
file vm_gc.h
to see the
complete list and documentation.
The VM exports two functions to
provide the global locking service for
the garbage collector:
vm_gc_lock_enum()
and
vm_gc_unlock_enum()
. These
two functions differ from plain system
locks in their ability to gracefully
interoperate with VM threading
services. In case of contention on the
GC lock, that is, when multiple threads
call vm_gc_lock_enum()
simultaneously, one thread gets the
lock, and others remain blocked. If the
thread that grabbed the GC lock does a
garbage collection, the blocked threads
are considered safely suspended. Other
ways to lock user threads for a long
time can lead to a deadlock because the
VM will have no way to find out whether
the thread is blocked or running.
A detailed description of GC procedure is given in the Developer's Guide.
DRLVM provides two functions to support thread suspension and root set enumeration simultaneously:
vm_enumerate_root_set_all_threads()
suspends all user threads and
initiates root set enumerationvm_resume_threads_after()
resumes execution of user
threadsThese functions effectively restrict the garbage collector to stop-the-world algorithms only.
This section gives step-by-step instructions on how to implement the garbage collection algorithm. The example shows a semispace copying collector.
Note
This example does not implement object finalization and weak references.
The heap is divided into two equally sized contiguous semispaces. During normal operation, only one semispace is used (current semispace), and the other one is reserved for garbage collection. Allocation requests are satisfied by contiguous allocation from the current semispace. Each application thread reserves a thread-local allocation buffer (TLAB) under a global lock, and serves most of the allocation requests without locking, by incrementing the allocation pointer local to the buffer.
When the application requests an allocation that does not fit into the remaining free space of the current semispace, a garbage collection is initiated. The current semispace becomes the evacuation space (fromspace), and the reserved semispace becomes the destination space (tospace). The VM suspends all application threads and enumerates root references.
The GC copies the objects reachable from root references to the destination space. When an object is copied from evacuation space to destination space, the GC installs the forwarding pointer in the old copy. Root references are updated to point to new object locations.
After the root set enumeration is complete, the GC scans objects in the destination space. Each reached object is copied to the destination space, the forwarding pointer is installed in the old copy, and the scanned object fields are updated. For objects with forwarding pointers installed, the GC updates object fields. In this way, the GC ensures that all live objects are copied to the destination space exactly once.
The destination space serves as a queue of objects to be scanned when more and more objects are copied to the destination space during heap traversal. Once all live objects are reached and copied, the scan queue stops growing, and the GC updates object fields only during the last part of the scanning process.
The GC completes the scanning process when the scan pointer reaches the allocation pointer in the destination space. At this stage, all live objects have been evacuated to the destination space, and the evacuation space can be safely reclaimed. The GC then changes the semispace roles: it uses the destination space for further allocation and reserves the evacuation space for the next garbage collection. The change of the semispace roles is commonly referred to as flip.
After the semispace flip, the GC resumes user threads.
Please refer to the excellent survey for detailed description of this algorithm and other basic garbage collection techniques, "Uniprocessor Garbage Collection Techniques", Paul R. Wilson.
The full source code of the
collector is available in
gc_copying.cpp
.
The structure TLS
(thread-local storage) is used for the
optimizing fast path allocation. The GC
allocates a buffer of free space from
the heap with appropriate locking and
further uses this buffer for
thread-local allocation.
// This structure is allocated for each user thread. // It contains the thread-local allocation area. struct TLS { byte* current; // the allocation pointer byte* limit; // the end of the allocation area };
Define the main GC structure to contain the Java heap and the data necessary for GC operation, as shown below.
// Encapsulates all GC data. struct GC { unsigned int semisize; // the size of the semispace unsigned int chunk_size; // the chunk size for thread-local chunks byte* space; // the pointer to the heap Lock lock; // the lock to protect global heap access byte* fromspace;// the allocation space byte* current; // the allocation marker byte* limit; // the allocation limit byte* tospace; // the evacuation space byte* scan; // the scan marker byte* copy; // the copy marker byte* toend; // the evacuation space limit // The list of thread-local storage (TLS) // structures allocated for user threads. std::list<TLS*> threads; void init(); // configures and initalizes GC void wrapup(); // destroys the heap // Allocates an object from a thread chunk // reserving space for TLS as needed. byte* alloc(unsigned size, TLS* tls); // Allocates space on the global heap. byte* galloc(unsigned size); byte* move(void*); // moves an object byte* forwarded(void*); // reads the forwarding pointer void root(void**); // handles a root reference void trace(byte*); // traces one object // Collects garbage and allocates the object. byte* collect_alloc(unsigned size, TLS* tls); std::list<InteriorPointer> interior_pointers; void repoint_all_roots_with_interior_points(); };
The following structure stores object information: the object field layout and the object size.
// Structure OI (from "object information") // is used to cache GC information for each Java class // loaded by the virtual machine. // Each VTable stores the pointer to an OI (Object information) structure. struct OI { char magic[8]; // used for debugging const char *name; // the class name (handy for debugging) bool is_array; // true if this structure describes array bool has_slots; // true if the described object has reference fields // (aka slots) and thus needs to be traced // during collection int size; // the object size or the array element size int* offsets; // zero-terminated list of slot offsets in an object // undefined for array };
The data stored in the
OI
structure is
initialized and accessed by the GC
only.
The following structures convey the static assumptions that GC makes about object layout. The VM must use the same object layout assumptions for the correct GC operation.
The VTable
structure
contains the virtual table of the
object methods, and is linked from the
object header. The VM reserves some
space (at least 4 bytes) for exclusive
use by GC. The GC uses 4 bytes of
GC-private space to put the pointer to
the object information structure
struct OI
.
// The VTable structure has 4 bytes reserved // for GC use at the beginning. // The pointer to the OI structure is stored there. struct VTable { OI* oi; // Other VTable fields are not used in GC. };
The GC assumes that each Java object
has a fixed header: (1) a pointer to
the VTable
structure, and
then a (2) 32 bit word with flags. The
25 highest bits are used by the VM
Thread Manager component to implement
Java monitors and 7 lowest bits are
used by GC and for storing the object
hash code.
// Describes the object header format assumed by GC. struct Object { VTable *vt; uint32 lockword; };
The array objects have the same header, and a 4 byte length field at the offset 8.
// Describes the array header format assumed by GC. struct Array { VTable *vt; uint32 lockword; uint32 length; };
Note
The layout described is valid for the IA-32 platform only.
A number of convenience functions
use object layout knowledge to perform
various data manipulations. The
function init_vt()
writes
the VTable pointer to an object.
void init_vt(Managed_Object_Handle p, Allocation_Handle ah) { Object* obj = (Object*)p; obj->vt = (VTable*)ah; }
The function obj_oi()
retrieves object information structure
pointer from an object.
OI* obj_oi(Managed_Object_Handle p) { Object* obj = (Object*)p; return obj->vt->oi; }
The function
array_length()
retrieves
the length of an array object.
int array_length(Managed_Object_Handle p) { Array* array = (Array*)p; return array->length; }
The function vt_oi()
retrieves the OI
structure
pointer from the VTable pointer.
OI* vt_oi(VTable_Handle p) { VTable* vt = (VTable*)p; return vt->oi; }
The function ah_oi()
retrieves the OI
structure
pointer using
Allocation_Handle
. On
32-bit architectures, the VTable
pointer is a 32-bit pointer, and
Allocation_Handle is a 32-bit
integer.
OI* ah_oi(Allocation_Handle ah) { // Allocation_Handle is a VTable pointer on 32-bit platforms. return vt_oi((VTable_Handle)ah); }
The object_size()
function computes the size of an
object. Array size is calculated by
summing the header size and the element
size multiplied by array length.
Afterwards the size is aligned to be
multiple of 4. The non-array object
size is cached in the OI structure.
int object_size (Managed_Object_Handle obj) { OI* oi = obj_oi(obj); if (oi->is_array) { // 4-byte alignment return ((oi->size * array_length(obj) + 12) + 3) & (~3); } else { return oi->size; } }
In this example, the garbage collector is created statically as a global instance of structure GC:
GC gc;
The function init()
statically configures size parameters.
Normally, this function uses the
function vm_get_property()
to read configuration options specified
as property values on the command line.
In this example, we use constant values
for simplicity.
void GC::init() { semisize = 500*1024*1024; chunk_size = 64*1024;
As the next step, the
init()
function allocates
space for the heap, divides it into two
semispaces, and initializes the
allocation semispace.
space = (byte*) malloc(semisize*2); assert(space); assert(((int)space & 3) == 0); fromspace = space; tospace = fromspace + semisize; assert(((int)tospace & 3) == 0); toend = tospace + semisize; INFO("heap size " << mb(2*semisize) << " Mb " << (void*)space << "-" << (void*)(space + 2*semisize)); current = fromspace; limit = fromspace + semisize; LOG("allocation from " << (void*)current << "-" << (void*)limit); memset(current, 0, limit - current); interior_pointers.clear(); }
The global allocation function uses
a lock to protect the heap from
simultaneous access from multiple
threads. The locking mechanism is
trivially implemented in a
platform-dependent way. See the full
source code in
gc_copying.cpp
.
byte* GC::galloc(unsigned size) { byte* r = NULL; lock.lock(); if (current + size <= limit) { r = current; current += size; } lock.unlock(); return r; }
The local allocation function uses
the thread-local allocation area for
object allocation, and uses
galloc()
to allocate a new
chunk for a thread-local allocation
area as needed.
byte* GC::alloc(unsigned size, TLS* tls) { byte* obj = NULL; assert(NULL == tls->current || fromspace <= tls->current); assert(NULL == tls->limit || tls->limit <= limit); if (tls->current + size <= tls->limit) { // Allocate from the thread-local chunk if possible. obj = tls->current; tls->current += size; return obj; } // Allocate "large" objects directly from the heap // bypassing the thread-local allocation buffer // to prevent inefficient handling of half-filled // thread-local allocation buffers. if (size >= chunk_size/4) { return gc.galloc(size); } // Allocate a new thread-local chunk. obj = gc.galloc(chunk_size); if (obj) { tls->current = obj + size; tls->limit = obj + chunk_size; } return obj; }
The forwarding pointers are installed in the lockword structure, the second word of an object.
byte* GC::forwarded (void* obj) { int* p = (int*)obj + 1; int lockword = *p; if (lockword & 1) return (byte*)(lockword & (~1)); else return NULL; }
The function move()
copies the object data to the
evacuation semispace and installs the
forwarding pointer in the old object
copy.
byte* GC::move (void* obj) { int size = object_size(obj); assert(tospace <= copy); assert(copy + size <= toend); byte* nobj = copy; TRACE2("gc.move", "move " << (void*)obj << " -> " << (void*)nobj); assert(((int)nobj & 3) == 0); memcpy(nobj, obj, size); copy += size; assert(((int)copy & 3) == 0); int* plockword = (int*)obj + 1; *plockword = ((int)nobj) | 1; return nobj; }
The function root()
handles one root during root set
enumeration. If the root points to an
object already reached, the root is
updated with the forwarded pointer
value. Otherwise, the GC moves the
object to the destination space and
installs the forwarding pointer in the
old object copy.
void GC::root(void** root) { byte* obj = (byte*)(*root); byte* nobj = forwarded(obj); if (NULL == nobj) { nobj = move(obj); } TRACE2("gc.root", "root " << root << " repointed from " << (void*)obj << " to " << (void*)nobj); *root = nobj; }
The function trace()
scans one object.
void GC::trace (byte* obj) { OI* oi = obj_oi(obj); TRACE2("gc.trace", "trace " << (void*)obj << " (" << (void*)object_size(obj) << ", " << oi->name << ")"); if (!oi->has_slots) return; if (oi->is_array) { int len = array_length(obj); int i; // Trace (len) elements starting from the offset 12. // NB: long[] and double[] start at offset 16 // but never need tracing. byte** elem = (byte**)(obj + 12); for (i = 0; i < len; i++, elem += 1) { if (NULL == *elem) continue; byte* nobj = forwarded(*elem); if (!nobj) nobj = move(*elem); TRACE2("gc.update", "elem " << i << " in array " << (void*)obj << " repointed from " << (void*)(*elem) << " to " << (void*)nobj); *elem = nobj; } } else { int* poff; // Use (offsets) array to get the list of reference // field offsets in an object. for (poff = oi->offsets; *poff; poff++) { byte** field = (byte**) (obj + *poff); if (NULL == *field) continue; byte* nobj = forwarded(*field); if (!nobj) nobj = move(*field); TRACE2("gc.update", "field " << *poff << " in object " << (void*)obj << " repointed from " << (void*)(*field) << " to " << (void*)nobj); *field = nobj; } } }
The function
collect_alloc()
is the
main function controlling garbage
collection. This function reclaims
unused memory and the retries the
allocation. The GC attempts to allocate
the memory before resuming other
threads. This prevents the thread that
triggered the garbage collection from
starving.
Note
The thread is starving when it gets no resources for a long time because other threads grab the resource before it can even try. If the garbage collector resumes user threads before retrying the allocation, these threads may use all available space quickly before the allocation succeeds. In this case, the allocation will fail for an indefinite number of times.
byte* GC::collect_alloc(unsigned size, TLS* tls) { scan = tospace; copy = tospace; toend = tospace + semisize; LOG("allocated " << (current - fromspace) << " bytes"); LOG("collection to " << (void*)tospace << "-" << (void*)toend); vm_enumerate_root_set_all_threads(); LOG("scan"); while (scan < copy) { trace(scan); scan += object_size(scan); } LOG("live " << (copy-tospace) << " bytes"); if( !interior_pointers.empty() ){ repoint_all_roots_with_interior_points(); } byte* swap = tospace; tospace = fromspace; fromspace = swap; current = copy; limit = fromspace + semisize; memset(current, 0, limit - current); LOG("allocation from " << (void*)current << "-" << (void*)limit); std::list<TLS*>::iterator i; int j; for (i = gc.threads.begin(), j = 0; i != gc.threads.end(); i++, j++) { (*i) -> current = NULL; (*i) -> limit = NULL; } LOG2("gc.threads", "reset thread allocation areas in " << j << " user threads"); byte* obj = NULL; if (size > 0) { // Allocate an object before resuming threads to maintain // "fairness" and prevent spurious out-of-memory errors. obj = alloc(size, tls); } vm_resume_threads_after(); return obj; }
The exported GC interface is mostly
implemented by delegating the task to
the method of the structure
GC
. The GC initialization
function init()
is called
from gc_init()
.
void gc_init() {
gc.init();
}
Thread local allocation areas are reset on thread creation and thread termination events.
void gc_thread_init(void* tp) {
TRACE2("gc.thread", "gc_thread_init " << tp);
TLS* tls = (TLS*) tp;
std::list<TLS*>::iterator i =
std::find(gc.threads.begin(), gc.threads.end(), tls);
assert(i == gc.threads.end());
gc.threads.push_back(tls);
tls->current = NULL;
tls->limit = NULL;
}
void gc_thread_kill(void* tp) {
TRACE2("gc.thread", "gc_thread_kill " << tp);
TLS* tls = (TLS*) tp;
std::list<TLS*>::iterator i =
std::find(gc.threads.begin(), gc.threads.end(), tls);
assert(i != gc.threads.end());
gc.threads.erase(i);
tls->current = NULL;
tls->limit = NULL;
}
The slow path allocation
function
gc_alloc()
checks
whether the allocation space is
exhausted and starts garbage
collection when necessary.
Managed_Object_Handle gc_alloc (unsigned size, Allocation_Handle ah, void *tp) { Managed_Object_Handle obj; TLS* tls = (TLS*) tp; // The next-to-highest bit of the size may be set // when allocating objects requiring finalization. // Ignore hint for now. size = size & 0x3fffffff; assert((size & 3) == 0); assert(ah_oi(ah)->is_array || size == ah_oi(ah)->size); // First, try the allocation obj = gc.alloc(size, tls); if (!obj) { // If the allocation failed, // grab the global GC lock. vm_gc_lock_enum(); // Multiple threads may try to get the GC lock. // Only one gets it and does a collection, and others get // blocked on vm_gc_lock_enum() during collection. // Retry the allocation while holding the GC lock. obj = gc.alloc(size, tls); // The allocation will succeed if another thread // has done collection while this thread was waiting for the GC lock. if (!obj) { // If the allocation failed, start a garbage collection. obj = gc.collect_alloc(size, tls); // NULL return value from collect_alloc() indicates out-of-memory. } vm_gc_unlock_enum(); } if (obj) init_vt(obj, ah); TRACE2("gc.alloc.slow", "gc_alloc(" << (void*)size << ", " << (*(OI**)ah)->name << ") = " << obj); assert(NULL == obj || (gc.fromspace <= obj && obj < gc.limit && ((int)obj & 3) == 0)); return obj; }
If the memory is exhausted,
the no-collection allocation
function
gc_alloc_fast()
returns NULL
, and
does not start garbage
collection.
Managed_Object_Handle gc_alloc_fast (unsigned size, Allocation_Handle ah, void *tp) { Managed_Object_Handle obj; TLS* tls = (TLS*) tp; size = size & 0x3fffffff; assert((size & 3) == 0); assert(ah_oi(ah)->is_array || size == ah_oi(ah)->size); obj = gc.alloc(size, tls); if (obj) init_vt(obj, ah); TRACE2("gc.alloc.fast", "gc_alloc_fast(" << (void*)size << ", " << (*(OI**)ah)->name << ") = " << obj); assert(NULL == obj || (gc.fromspace <= obj && obj < gc.limit && ((int)obj & 3) == 0)); return obj; }
The root set enumeration
function passes the root
reference to the
root()
function.
void gc_add_root_set_entry(Managed_Object_Handle *ref, Boolean is_pinned) { assert(!is_pinned); TRACE2("gc.root", "gc_add_root_set_entry " << ref << " -> " << *ref); if (NULL == *ref) return; gc.root(ref); }
The function
build_slot_offset_array()
is used to construct a
NULL
-terminated
list of offsets of reference
fields.
static int *build_slot_offset_array(Class_Handle ch) { unsigned num_ref_fields = 0; // Get the total number of fields including primitive fields. unsigned num_fields = class_num_instance_fields_recursive(ch); // Compute the number of reference fields. unsigned int i; for (i = 0; i < num_fields; i++) { // For each field, get its handle and check // whether it's a reference type. Field_Handle fh = class_get_instance_field_recursive(ch, i); if (field_is_reference(fh)) { num_ref_fields++; } } if (0 == num_ref_fields) return NULL; // Allocate the offsets array. int* ref_array = (int*) malloc((num_ref_fields+1) * sizeof(int)); // For each reference field, store its offset // into the offsets array. int* p = ref_array; for (i = 0; i < num_fields; i++) { Field_Handle fh = class_get_instance_field_recursive(ch, i); if (field_is_reference(fh)) { *p = field_get_offset(fh); p++; } } // It is 0 delimited. *p = 0; return ref_array; }
The GC caches object layout
information when the function
gc_class_prepared()
is called.
void gc_class_prepared (Class_Handle ch, VTable_Handle vth) { TRACE2("gc.prepared", "gc_class_prepared(" << class_get_name(ch) << ")"); OI** vt = (OI**) vth; OI* oi = new OI; *vt = oi; memset(oi, 0, sizeof(OI)); strcpy(oi->magic, " OI "); oi->name = class_get_name(ch); if (class_is_array(ch)) { oi->is_array = true; // Store the array element size in the OI structure > size. oi->size = class_element_size(ch); // Reference arrays have slots, non-reference arrays don't. oi->has_slots = !class_is_non_ref_array(ch); assert(NULL == oi->offsets); } else { oi->is_array = false; // Store the object size in the OI structure > size. oi->size = class_get_boxed_data_size(ch); assert((oi->size & 3) == 0); oi->offsets = build_slot_offset_array(ch); oi->has_slots = (oi->offsets != NULL); } }
The function
gc_force_gc()
starts a forced garbage
collection using the global GC
lock to ensure that only one
thread is doing a collection at
any time. It passes null
arguments to
collect_alloc()
,
because it requires no
allocation.
void gc_force_gc () { vm_gc_lock_enum(); gc.collect_alloc(0, NULL); vm_gc_unlock_enum(); }
Other functions of the GC
interface are empty or trivial,
and not described in this
document. You can see the full
listing in the
gc_copying.cpp
file.
After you completed coding the garbage collector, you can build a GC dynamic library, as described above, by typing
build$ build.bat -DCOMPONENTS=vm.gc_copying
This section describes how to run the DRL virtual machine with the custom garbage collector library.
You can specify the name of the
dynamic library on the command line.
For example, to load a GC
gc_copying.dll
, execute
the following:
java -Dvm.dlls=gc_copying.dll Hello
The virtual machine searches for a
dynamic library
gc_copying.dll
in the
default locations, that is, the value
for the PATH variable and the location
of executable java.exe
. The
default garbage collector is
gc.dll
located in the same
bin/
directory as
java.exe
.