Skip to content

Commit

Permalink
Implement initial RAM class persistence feature
Browse files Browse the repository at this point in the history
With the motivation of reducing startup time, this patch implements
initial support for a mechanism to persist RAM classes to disk during
a training run and load the persisted snapshot on subsequent
identical production runs.

Towards this goal, this patch:
- Identifies an -Xsnapshot= command-line option that enables the
  snapshotting functionality and takes a string parameter that
  specifies a file path where the snapshot image is/will be.
- Adds SnapshotFileFormat.h file that specifies the format of the
  snapshot image that is persisted to disk. It describes the J9JavaVM
  members that are supported, namely immortal J9ClassLoader objects
  and the J9Class objects that they load.
- Adds the VMSnapshotImpl files that specify and implement the
  snapshotting API and include suballocators wherein the data to include
  within a snapshot is allocated.
- Ensures correct allocation/initialization of the immortal J9ClassLoaders
  and memory segments snapshotting and restoring.
- Enables taking a snapshot on VM shutdown, and restoring during startup.

Co-authored-by: Tobi Ajila <[email protected]>
Co-authored-by: Babneet Singh <[email protected]>
Co-authored-by: Nathan Henderson <[email protected]>
Signed-off-by: Nathan Henderson <[email protected]>
  • Loading branch information
4 people committed Nov 1, 2024
1 parent a08b536 commit 85a3135
Show file tree
Hide file tree
Showing 30 changed files with 2,237 additions and 102 deletions.
5 changes: 5 additions & 0 deletions buildenv/jenkins/variables/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ valhalla_standard:
ojdk292:
extra_configure_options: '--enable-openjdk-methodhandles'
#========================================#
# RAM Persistence
#========================================#
snapshots:
extra_configure_options: '--enable-snapshots'
#========================================#
# Linux PPCLE 64bits
#========================================#
ppc64le_linux:
Expand Down
4 changes: 4 additions & 0 deletions buildspecs/j9.flags
Original file line number Diff line number Diff line change
Expand Up @@ -1868,6 +1868,10 @@ Only available on zOS</description>
<require flag="interp_jniSupport"/>
</requires>
</flag>
<flag id="opt_snapshots">
<description>Enable support for RAM class snapshot images</description>
<ifRemoved>Disable support for RAM class snapshot images</ifRemoved>
</flag>
<flag id="opt_srpAvlTreeSupport">
<description>Include support for AVL trees with SRPs</description>
<ifRemoved>Use normal AVL trees with direct pointers</ifRemoved>
Expand Down
1 change: 1 addition & 0 deletions runtime/cmake/caches/common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ set(J9VM_OPT_OPENJDK_METHODHANDLE OFF CACHE BOOL "")
set(J9VM_OPT_REFLECT ON CACHE BOOL "")
set(J9VM_OPT_ROM_IMAGE_SUPPORT ON CACHE BOOL "")
set(J9VM_OPT_SHARED_CLASSES ON CACHE BOOL "")
set(J9VM_OPT_SNAPSHOTS OFF CACHE BOOL "")
set(J9VM_OPT_SIDECAR ON CACHE BOOL "")
set(J9VM_OPT_SRP_AVL_TREE_SUPPORT ON CACHE BOOL "")
set(J9VM_OPT_STRING_COMPRESSION ON CACHE BOOL "")
Expand Down
2 changes: 2 additions & 0 deletions runtime/cmake/options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ option(J9VM_OPT_VALHALLA_FLATTENABLE_VALUE_TYPES "Enables support for Project Va
option(J9VM_OPT_ROM_IMAGE_SUPPORT "Controls if the VM includes basic support for linked rom images")
option(J9VM_OPT_SHARED_CLASSES "Support for class sharing")

option(J9VM_OPT_SNAPSHOTS "Support for RAM class snapshot images")

option(J9VM_OPT_ZIP_SUPPORT "Controls if the VM includes zip reading and caching support. (implies dynamic loading)")
option(J9VM_OPT_ZLIB_COMPRESSION "Controls if the compression routines in zlib are included.")
option(J9VM_OPT_ZLIB_SUPPORT "Controls if the VM includes the zlib compression library.")
Expand Down
66 changes: 38 additions & 28 deletions runtime/gc_modron_startup/mminit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,14 @@ gcCleanupHeapStructures(J9JavaVM * vm)
gam->flushAllocationContextsForShutdown(&env);
}

if (vm->memorySegments) {
vm->internalVMFunctions->freeMemorySegmentList(vm, vm->memorySegments);
}
if (vm->classMemorySegments) {
vm->internalVMFunctions->freeMemorySegmentList(vm, vm->classMemorySegments);
if (!IS_RESTORE_RUN(vm)) {
if (NULL != vm->memorySegments) {
vm->internalVMFunctions->freeMemorySegmentList(vm, vm->memorySegments);
}

if (NULL != vm->classMemorySegments) {
vm->internalVMFunctions->freeMemorySegmentList(vm, vm->classMemorySegments);
}
}

#if defined(J9VM_GC_FINALIZATION)
Expand Down Expand Up @@ -555,30 +558,37 @@ gcInitializeHeapStructures(J9JavaVM *vm)
MM_GCExtensions *extensions = MM_GCExtensions::getExtensions(vm);
J9VMDllLoadInfo *loadInfo = getGCDllLoadInfo(vm);

/* For now, number of segments to default in pool */
if ((vm->memorySegments = vm->internalVMFunctions->allocateMemorySegmentList(vm, 10, OMRMEM_CATEGORY_VM)) == NULL) {
vm->internalVMFunctions->setErrorJ9dll(
PORTLIB,
loadInfo,
j9nls_lookup_message(
J9NLS_DO_NOT_PRINT_MESSAGE_TAG | J9NLS_DO_NOT_APPEND_NEWLINE,
J9NLS_GC_FAILED_TO_ALLOCATE_VM_MEMORY_SEGMENTS,
"Failed to allocate VM memory segments."),
FALSE);
goto error;
}
/* By this point during a restore run, the memory segments are already allocated
* and initialized.
*/
if (!IS_RESTORE_RUN(vm)) {
/* For now, set the number of segments to a default (= 10) in the pool. */
U_32 defaultSegments = 10;
vm->memorySegments = vm->internalVMFunctions->allocateMemorySegmentList(vm, defaultSegments, OMRMEM_CATEGORY_VM);
if (NULL == vm->memorySegments) {
vm->internalVMFunctions->setErrorJ9dll(
PORTLIB,
loadInfo,
j9nls_lookup_message(
J9NLS_DO_NOT_PRINT_MESSAGE_TAG | J9NLS_DO_NOT_APPEND_NEWLINE,
J9NLS_GC_FAILED_TO_ALLOCATE_VM_MEMORY_SEGMENTS,
"Failed to allocate VM memory segments."),
FALSE);
goto error;
}

/* For now, number of segments to default in pool */
if ((vm->classMemorySegments = vm->internalVMFunctions->allocateMemorySegmentListWithFlags(vm, 10, MEMORY_SEGMENT_LIST_FLAG_SORT, J9MEM_CATEGORY_CLASSES)) == NULL) {
vm->internalVMFunctions->setErrorJ9dll(
PORTLIB,
loadInfo,
j9nls_lookup_message(
J9NLS_DO_NOT_PRINT_MESSAGE_TAG | J9NLS_DO_NOT_APPEND_NEWLINE,
J9NLS_GC_FAILED_TO_ALLOCATE_VM_CLASS_MEMORY_SEGMENTS,
"Failed to allocate VM class memory segments."),
FALSE);
goto error;
vm->classMemorySegments = vm->internalVMFunctions->allocateMemorySegmentListWithFlags(vm, defaultSegments, MEMORY_SEGMENT_LIST_FLAG_SORT, J9MEM_CATEGORY_CLASSES);
if (NULL == vm->classMemorySegments) {
vm->internalVMFunctions->setErrorJ9dll(
PORTLIB,
loadInfo,
j9nls_lookup_message(
J9NLS_DO_NOT_PRINT_MESSAGE_TAG | J9NLS_DO_NOT_APPEND_NEWLINE,
J9NLS_GC_FAILED_TO_ALLOCATE_VM_CLASS_MEMORY_SEGMENTS,
"Failed to allocate VM class memory segments."),
FALSE);
goto error;
}
}

/* j9gc_initialize_heap is now called from gcInitializeDefaults */
Expand Down
1 change: 1 addition & 0 deletions runtime/include/j9cfg.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ extern "C" {
#cmakedefine J9VM_OPT_RESOURCE_MANAGED
#cmakedefine J9VM_OPT_ROM_IMAGE_SUPPORT
#cmakedefine J9VM_OPT_SHARED_CLASSES
#cmakedefine J9VM_OPT_SNAPSHOTS
#cmakedefine J9VM_OPT_SHR_MSYNC_SUPPORT
#cmakedefine J9VM_OPT_SIDECAR
#cmakedefine J9VM_OPT_SRP_AVL_TREE_SUPPORT
Expand Down
17 changes: 17 additions & 0 deletions runtime/j9vm/jvm.c
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,9 @@ typedef struct J9SpecialArguments {
IDATA *ibmMallocTraceSet;
const char *executableJarPath;
BOOLEAN captureCommandLine;
#if defined(J9VM_OPT_SNAPSHOTS)
const char *vmSnapshotFilePath;
#endif /* defined(J9VM_OPT_SNAPSHOTS) */
} J9SpecialArguments;
/**
* Look for special options:
Expand Down Expand Up @@ -1602,6 +1605,10 @@ initialArgumentScan(JavaVMInitArgs *args, J9SpecialArguments *specialArgs)
specialArgs->captureCommandLine = TRUE;
} else if (0 == strcmp(args->options[argCursor].optionString, VMOPT_XXNOOPENJ9COMMANDLINEENV)) {
specialArgs->captureCommandLine = FALSE;
#if defined(J9VM_OPT_SNAPSHOTS)
} else if (0 == strncmp(args->options[argCursor].optionString, VMOPT_XSNAPSHOT, strlen(VMOPT_XSNAPSHOT))) {
specialArgs->vmSnapshotFilePath = args->options[argCursor].optionString + strlen(VMOPT_XSNAPSHOT);
#endif /* defined(J9VM_OPT_SNAPSHOTS) */
}
}

Expand Down Expand Up @@ -1975,6 +1982,9 @@ JNI_CreateJavaVM_impl(JavaVM **pvm, void **penv, void *vm_args, BOOLEAN isJITSer
specialArgs.executableJarPath = NULL;
specialArgs.ibmMallocTraceSet = &ibmMallocTraceSet;
specialArgs.captureCommandLine = TRUE;
#if defined(J9VM_OPT_SNAPSHOTS)
specialArgs.vmSnapshotFilePath = NULL;
#endif /* defined(J9VM_OPT_SNAPSHOTS) */
#if defined(J9ZOS390)
/*
* Temporarily disable capturing the command line on z/OS.
Expand Down Expand Up @@ -2232,6 +2242,13 @@ JNI_CreateJavaVM_impl(JavaVM **pvm, void **penv, void *vm_args, BOOLEAN isJITSer
launcherArgumentsSize = initialArgumentScan(args, &specialArgs);
localVerboseLevel = specialArgs.localVerboseLevel;

#if defined(J9VM_OPT_SNAPSHOTS)
if (NULL != specialArgs.vmSnapshotFilePath) {
createParams.flags |= J9_CREATEJAVAVM_SNAPSHOT;
createParams.vmSnapshotFilePath = specialArgs.vmSnapshotFilePath;
}
#endif /* defined(J9VM_OPT_SNAPSHOTS) */

if (VERBOSE_INIT == localVerboseLevel) {
createParams.flags |= J9_CREATEJAVAVM_VERBOSE_INIT;
}
Expand Down
6 changes: 4 additions & 2 deletions runtime/jcl/common/jclcinit.c
Original file line number Diff line number Diff line change
Expand Up @@ -725,8 +725,10 @@ initializeRequiredClasses(J9VMThread *vmThread, char* dllName)
*/
vm->extendedRuntimeFlags |= J9_EXTENDED_RUNTIME_CLASS_OBJECT_ASSIGNED;

if (vmFuncs->internalCreateBaseTypePrimitiveAndArrayClasses(vmThread) != 0) {
return 1;
if (!IS_RESTORE_RUN(vm)) {
if (0 != vmFuncs->internalCreateBaseTypePrimitiveAndArrayClasses(vmThread)) {
return 1;
}
}

/* Initialize early since sendInitialize() uses this */
Expand Down
28 changes: 27 additions & 1 deletion runtime/jcl/common/jcldefine.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,33 @@ defineClassCommon(JNIEnv *env, jobject classLoaderObject,
/* Bad, we have already defined this class - fail */
omrthread_monitor_exit(vm->classTableMutex);
if (J9_ARE_NO_BITS_SET(*options, J9_FINDCLASS_FLAG_NAME_IS_INVALID)) {
vmFuncs->setCurrentExceptionNLSWithArgs(currentThread, J9NLS_JCL_DUPLICATE_CLASS_DEFINITION, J9VMCONSTANTPOOL_JAVALANGLINKAGEERROR, utf8Length, utf8Name);
/* TODO: This path is taken if Classloader.findClass is called on a persisted class.
* Once class objects are persisted, reaching this point is an error.
*/
if (!IS_RESTORE_RUN(vm)) {
vmFuncs->setCurrentExceptionNLSWithArgs(
currentThread,
J9NLS_JCL_DUPLICATE_CLASS_DEFINITION,
J9VMCONSTANTPOOL_JAVALANGLINKAGEERROR,
utf8Length,
utf8Name);
#if defined(J9VM_OPT_SNAPSHOTS)
} else {
clazz = vmFuncs->hashClassTableAt(classLoader, utf8Name, utf8Length);

if (!vmFuncs->loadWarmClassFromSnapshot(currentThread, classLoader, clazz)) {
clazz = NULL;
}

clazz = vmFuncs->initializeSnapshotClassObject(vm, classLoader, clazz);
if (NULL != protectionDomain) {
J9VMJAVALANGCLASS_SET_PROTECTIONDOMAIN(
currentThread,
clazz->classObject,
J9_JNI_UNWRAP_REFERENCE(protectionDomain));
}
#endif /* defined(J9VM_OPT_SNAPSHOTS) */
}
}
goto done;
}
Expand Down
30 changes: 25 additions & 5 deletions runtime/jcl/common/stdinit.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ standardInit(J9JavaVM *vm, char *dllName)
}
}
/* Now create the classPathEntries */
if (initializeBootstrapClassPath(vm)) {
if (initializeBootstrapClassPath(vm) && !IS_RESTORE_RUN(vm)) {
goto _fail;
}
#endif
Expand Down Expand Up @@ -453,14 +453,24 @@ internalInitializeJavaLangClassLoader(JNIEnv * env)

vmFuncs->internalEnterVMFromJNI(vmThread);

vm->applicationClassLoader = J9VMJAVALANGCLASSLOADER_VMREF(vmThread, J9_JNI_UNWRAP_REFERENCE(appClassLoader));
/* Always use the persisted applicationClassLoader in restore runs. */
if (!IS_RESTORE_RUN(vm)) {
vm->applicationClassLoader = J9VMJAVALANGCLASSLOADER_VMREF(vmThread, J9_JNI_UNWRAP_REFERENCE(appClassLoader));
}

if (NULL == vm->applicationClassLoader) {
/* CMVC 201518
* applicationClassLoader may be null due to lazy classloader initialization. Initialize
* the applicationClassLoader now or vm will start throwing NoClassDefFoundException.
*/
vm->applicationClassLoader = (void*) (UDATA)(vmFuncs->internalAllocateClassLoader(vm, J9_JNI_UNWRAP_REFERENCE(appClassLoader)));
if (!IS_RESTORE_RUN(vm)) {
vm->applicationClassLoader = (void *)(UDATA)(vmFuncs->internalAllocateClassLoader(vm, J9_JNI_UNWRAP_REFERENCE(appClassLoader)));
#if defined(J9VM_OPT_SNAPSHOTS)
} else {
vmFuncs->initializeSnapshotClassLoaderObject(vm, vm->applicationClassLoader, J9_JNI_UNWRAP_REFERENCE(appClassLoader));
#endif /* defined(J9VM_OPT_SNAPSHOTS) */
}

if (NULL != vmThread->currentException) {
/* while this exception check and return statement seem un-necessary, it is added to prevent
* oversights if anybody adds more code in the future.
Expand All @@ -479,10 +489,20 @@ internalInitializeJavaLangClassLoader(JNIEnv * env)
classLoaderParentObject = J9VMJAVALANGCLASSLOADER_PARENT(vmThread, classLoaderObject);
}

vm->extensionClassLoader = J9VMJAVALANGCLASSLOADER_VMREF(vmThread, classLoaderObject);
/* Restore runs use the persisted extensionClassLoader. */
if (!IS_RESTORE_RUN(vm)) {
vm->extensionClassLoader = J9VMJAVALANGCLASSLOADER_VMREF(vmThread, classLoaderObject);
}

if (NULL == vm->extensionClassLoader) {
vm->extensionClassLoader = (void*) (UDATA)(vmFuncs->internalAllocateClassLoader(vm, classLoaderObject));
if (!IS_RESTORE_RUN(vm)) {
vm->extensionClassLoader = (void *)(UDATA)(vmFuncs->internalAllocateClassLoader(vm, classLoaderObject));
#if defined(J9VM_OPT_SNAPSHOTS)
} else {
vmFuncs->initializeSnapshotClassLoaderObject(vm, vm->extensionClassLoader, classLoaderObject);
#endif /* defined(J9VM_OPT_SNAPSHOTS) */
}

if (NULL != vmThread->currentException) {
/* while this exception check and return statement seem un-necessary, it is added to prevent
* oversights if anybody adds more code in the future.
Expand Down
Loading

0 comments on commit 85a3135

Please sign in to comment.