# HG changeset patch # User ccheung # Date 1558106995 25200 # Node ID 1512d88b24c6475220ea88d5fddc383b8bc8c064 # Parent d4e7ccaf144530260492d5d1caaf4948287781dd 8207812: Implement Dynamic CDS Archive Summary: Improve the usability of AppCDS Reviewed-by: acorn, jiangli, mseledtsov Contributed-by: ioi.lam@oracle.com, jianglizhou@google.com, calvin.cheung@oracle.com diff -r d4e7ccaf1445 -r 1512d88b24c6 make/hotspot/lib/JvmFeatures.gmk --- a/make/hotspot/lib/JvmFeatures.gmk Fri May 17 10:48:02 2019 -0400 +++ b/make/hotspot/lib/JvmFeatures.gmk Fri May 17 08:29:55 2019 -0700 @@ -111,6 +111,7 @@ JVM_EXCLUDE_FILES += \ classListParser.cpp \ classLoaderExt.cpp \ + dynamicArchive.cpp \ filemap.cpp \ heapShared.cpp \ metaspaceShared.cpp \ diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/classListParser.cpp --- a/src/hotspot/share/classfile/classListParser.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/classListParser.cpp Fri May 17 08:29:55 2019 -0700 @@ -295,14 +295,14 @@ if (!is_id_specified()) { error("If source location is specified, id must be also specified"); } - InstanceKlass* k = ClassLoaderExt::load_class(class_name, _source, CHECK_NULL); - if (strncmp(_class_name, "java/", 5) == 0) { log_info(cds)("Prohibited package for non-bootstrap classes: %s.class from %s", _class_name, _source); return NULL; } + InstanceKlass* k = ClassLoaderExt::load_class(class_name, _source, CHECK_NULL); + if (k != NULL) { if (k->local_interfaces()->length() != _interfaces->length()) { print_specified_interfaces(); @@ -461,4 +461,3 @@ ShouldNotReachHere(); return NULL; } - diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/classLoader.cpp --- a/src/hotspot/share/classfile/classLoader.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/classLoader.cpp Fri May 17 08:29:55 2019 -0700 @@ -36,6 +36,7 @@ #include "classfile/klassFactory.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" #include "classfile/vmSymbols.hpp" #include "compiler/compileBroker.hpp" #include "interpreter/bytecodeStream.hpp" @@ -468,7 +469,7 @@ #if INCLUDE_CDS void ClassLoader::exit_with_path_failure(const char* error, const char* message) { - assert(DumpSharedSpaces, "only called at dump time"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "only called at dump time"); tty->print_cr("Hint: enable -Xlog:class+path=info to diagnose the failure"); vm_exit_during_initialization(error, message); } @@ -534,7 +535,7 @@ trace_class_path("bootstrap loader class path=", sys_class_path); } #if INCLUDE_CDS - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { _shared_paths_misc_info->add_boot_classpath(sys_class_path); } #endif @@ -550,16 +551,16 @@ return _shared_paths_misc_info->buffer(); } -bool ClassLoader::check_shared_paths_misc_info(void *buf, int size) { +bool ClassLoader::check_shared_paths_misc_info(void *buf, int size, bool is_static) { SharedPathsMiscInfo* checker = new SharedPathsMiscInfo((char*)buf, size); - bool result = checker->check(); + bool result = checker->check(is_static); delete checker; return result; } void ClassLoader::setup_app_search_path(const char *class_path) { - assert(DumpSharedSpaces, "Sanity"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "Sanity"); Thread* THREAD = Thread::current(); int len = (int)strlen(class_path); @@ -587,7 +588,7 @@ void ClassLoader::add_to_module_path_entries(const char* path, ClassPathEntry* entry) { assert(entry != NULL, "ClassPathEntry should not be NULL"); - assert(DumpSharedSpaces, "dump time only"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "dump time only"); // The entry does not exist, add to the list if (_module_path_entries == NULL) { @@ -601,7 +602,7 @@ // Add a module path to the _module_path_entries list. void ClassLoader::update_module_path_entry_list(const char *path, TRAPS) { - assert(DumpSharedSpaces, "dump time only"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "dump time only"); struct stat st; if (os::stat(path, &st) != 0) { tty->print_cr("os::stat error %d (%s). CDS dump aborted (path was \"%s\").", @@ -709,7 +710,7 @@ bool set_base_piece = true; #if INCLUDE_CDS - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { if (!Arguments::has_jimage()) { vm_exit_during_initialization("CDS is not supported in exploded JDK build", NULL); } @@ -976,7 +977,7 @@ return true; } else { #if INCLUDE_CDS - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { _shared_paths_misc_info->add_nonexist_path(path); } #endif @@ -1334,6 +1335,10 @@ // appear in the _patch_mod_entries. The runtime shared class visibility // check will determine if a shared class is visible based on the runtime // environemnt, including the runtime --patch-module setting. + // + // DynamicDumpSharedSpaces requires UseSharedSpaces to be enabled. Since --patch-module + // is not supported with UseSharedSpaces, it is not supported with DynamicDumpSharedSpaces. + assert(!DynamicDumpSharedSpaces, "sanity"); if (!DumpSharedSpaces) { stream = search_module_entries(_patch_mod_entries, class_name, file_name, CHECK_NULL); } @@ -1423,7 +1428,7 @@ // Record the shared classpath index and loader type for classes loaded // by the builtin loaders at dump time. void ClassLoader::record_result(InstanceKlass* ik, const ClassFileStream* stream, TRAPS) { - assert(DumpSharedSpaces, "sanity"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "sanity"); assert(stream != NULL, "sanity"); if (ik->is_unsafe_anonymous()) { @@ -1513,6 +1518,8 @@ // user defined classloader. if (classpath_index < 0) { assert(ik->shared_classpath_index() < 0, "Sanity"); + ik->set_shared_classpath_index(UNREGISTERED_INDEX); + SystemDictionaryShared::set_shared_class_misc_info(ik, (ClassFileStream*)stream); return; } } else { @@ -1595,7 +1602,7 @@ load_jimage_library(); #if INCLUDE_CDS // initialize search path - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { _shared_paths_misc_info = new SharedPathsMiscInfo(); } #endif @@ -1604,14 +1611,14 @@ #if INCLUDE_CDS void ClassLoader::initialize_shared_path() { - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { ClassLoaderExt::setup_search_paths(); _shared_paths_misc_info->write_jint(0); // see comments in SharedPathsMiscInfo::check() } } void ClassLoader::initialize_module_path(TRAPS) { - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { ClassLoaderExt::setup_module_paths(THREAD); FileMapInfo::allocate_shared_path_table(); } @@ -1677,6 +1684,7 @@ // entries will be added to the exploded build array. if (!has_jrt_entry()) { assert(!DumpSharedSpaces, "DumpSharedSpaces not supported with exploded module builds"); + assert(!DynamicDumpSharedSpaces, "DynamicDumpSharedSpaces not supported with exploded module builds"); assert(!UseSharedSpaces, "UsedSharedSpaces not supported with exploded module builds"); // Set up the boot loader's _exploded_entries list. Note that this gets // done before loading any classes, by the same thread that will diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/classLoader.hpp --- a/src/hotspot/share/classfile/classLoader.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/classLoader.hpp Fri May 17 08:29:55 2019 -0700 @@ -398,7 +398,8 @@ // Helper function used by CDS code to get the number of module path // entries during shared classpath setup time. static int num_module_path_entries() { - assert(DumpSharedSpaces, "Should only be called at CDS dump time"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, + "Should only be called at CDS dump time"); int num_entries = 0; ClassPathEntry* e= ClassLoader::_module_path_entries; while (e != NULL) { @@ -410,7 +411,7 @@ static void finalize_shared_paths_misc_info(); static int get_shared_paths_misc_info_size(); static void* get_shared_paths_misc_info(); - static bool check_shared_paths_misc_info(void* info, int size); + static bool check_shared_paths_misc_info(void* info, int size, bool is_static); static void exit_with_path_failure(const char* error, const char* message); static char* skip_uri_protocol(char* source); static void record_result(InstanceKlass* ik, const ClassFileStream* stream, TRAPS); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/classLoader.inline.hpp --- a/src/hotspot/share/classfile/classLoader.inline.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/classLoader.inline.hpp Fri May 17 08:29:55 2019 -0700 @@ -62,7 +62,8 @@ // entries during shared classpath setup time. inline int ClassLoader::num_boot_classpath_entries() { - assert(DumpSharedSpaces, "Should only be called at CDS dump time"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, + "Should only be called at CDS dump time"); assert(has_jrt_entry(), "must have a java runtime image"); int num_entries = 1; // count the runtime image ClassPathEntry* e = ClassLoader::_first_append_entry; @@ -84,7 +85,8 @@ // Helper function used by CDS code to get the number of app classpath // entries during shared classpath setup time. inline int ClassLoader::num_app_classpath_entries() { - assert(DumpSharedSpaces, "Should only be called at CDS dump time"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, + "Should only be called at CDS dump time"); int num_entries = 0; ClassPathEntry* e= ClassLoader::_app_classpath_entries; while (e != NULL) { diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/classLoaderExt.cpp --- a/src/hotspot/share/classfile/classLoaderExt.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/classLoaderExt.cpp Fri May 17 08:29:55 2019 -0700 @@ -62,7 +62,8 @@ } void ClassLoaderExt::setup_app_search_path() { - assert(DumpSharedSpaces, "this function is only used with -Xshare:dump"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, + "this function is only used at CDS dump time"); _app_class_paths_start_index = ClassLoader::num_boot_classpath_entries(); char* app_class_path = os::strdup(Arguments::get_appclasspath()); @@ -92,7 +93,8 @@ } } void ClassLoaderExt::setup_module_paths(TRAPS) { - assert(DumpSharedSpaces, "this function is only used with -Xshare:dump"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, + "this function is only used with CDS dump time"); _app_module_paths_start_index = ClassLoader::num_boot_classpath_entries() + ClassLoader::num_app_classpath_entries(); Handle system_class_loader (THREAD, SystemDictionary::java_system_loader()); @@ -227,7 +229,7 @@ void ClassLoaderExt::record_result(const s2 classpath_index, InstanceKlass* result, TRAPS) { - assert(DumpSharedSpaces, "Sanity"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "Sanity"); // We need to remember where the class comes from during dumping. oop loader = result->class_loader(); @@ -301,8 +303,6 @@ tty->print_cr("Preload Error: Failed to load %s", class_name); return NULL; } - result->set_shared_classpath_index(UNREGISTERED_INDEX); - SystemDictionaryShared::set_shared_class_misc_info(result, stream); return result; } diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/compactHashtable.cpp --- a/src/hotspot/share/classfile/compactHashtable.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/compactHashtable.cpp Fri May 17 08:29:55 2019 -0700 @@ -27,6 +27,7 @@ #include "classfile/compactHashtable.hpp" #include "classfile/javaClasses.hpp" #include "logging/logMessage.hpp" +#include "memory/dynamicArchive.hpp" #include "memory/heapShared.inline.hpp" #include "memory/metadataFactory.hpp" #include "memory/metaspaceShared.hpp" @@ -39,12 +40,14 @@ // // The compact hash table writer implementations // -CompactHashtableWriter::CompactHashtableWriter(int num_buckets, +CompactHashtableWriter::CompactHashtableWriter(int num_entries, CompactHashtableStats* stats) { - assert(DumpSharedSpaces, "dump-time only"); - assert(num_buckets > 0, "no buckets"); - _num_buckets = num_buckets; - _num_entries = 0; + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "dump-time only"); + assert(num_entries >= 0, "sanity"); + _num_buckets = calculate_num_buckets(num_entries); + assert(_num_buckets > 0, "no buckets"); + + _num_entries_written = 0; _buckets = NEW_C_HEAP_ARRAY(GrowableArray*, _num_buckets, mtSymbol); for (int i=0; i<_num_buckets; i++) { _buckets[i] = new (ResourceObj::C_HEAP, mtSymbol) GrowableArray(0, true, mtSymbol); @@ -67,11 +70,24 @@ FREE_C_HEAP_ARRAY(GrowableArray*, _buckets); } +size_t CompactHashtableWriter::estimate_size(int num_entries) { + int num_buckets = calculate_num_buckets(num_entries); + size_t bucket_bytes = MetaspaceShared::ro_array_bytesize(num_buckets + 1); + + // In worst case, we have no VALUE_ONLY_BUCKET_TYPE, so each entry takes 2 slots + int entries_space = 2 * num_entries; + size_t entry_bytes = MetaspaceShared::ro_array_bytesize(entries_space); + + return bucket_bytes + + entry_bytes + + SimpleCompactHashtable::calculate_header_size(); +} + // Add a symbol entry to the temporary hash table void CompactHashtableWriter::add(unsigned int hash, u4 value) { int index = hash % _num_buckets; _buckets[index]->append_if_missing(Entry(hash, value)); - _num_entries++; + _num_entries_written++; } void CompactHashtableWriter::allocate_table() { @@ -81,7 +97,7 @@ int bucket_size = bucket->length(); if (bucket_size == 1) { entries_space++; - } else { + } else if (bucket_size > 1) { entries_space += 2 * bucket_size; } } @@ -96,7 +112,7 @@ _stats->bucket_count = _num_buckets; _stats->bucket_bytes = _compact_buckets->size() * BytesPerWord; - _stats->hashentry_count = _num_entries; + _stats->hashentry_count = _num_entries_written; _stats->hashentry_bytes = _compact_entries->size() * BytesPerWord; } @@ -144,19 +160,19 @@ dump_table(&summary); int table_bytes = _stats->bucket_bytes + _stats->hashentry_bytes; - address base_address = address(MetaspaceShared::shared_rs()->base()); - cht->init(base_address, _num_entries, _num_buckets, + address base_address = address(SharedBaseAddress); + cht->init(base_address, _num_entries_written, _num_buckets, _compact_buckets->data(), _compact_entries->data()); LogMessage(cds, hashtables) msg; if (msg.is_info()) { double avg_cost = 0.0; - if (_num_entries > 0) { - avg_cost = double(table_bytes)/double(_num_entries); + if (_num_entries_written > 0) { + avg_cost = double(table_bytes)/double(_num_entries_written); } msg.info("Shared %s table stats -------- base: " PTR_FORMAT, table_name, (intptr_t)base_address); - msg.info("Number of entries : %9d", _num_entries); + msg.info("Number of entries : %9d", _num_entries_written); msg.info("Total bytes used : %9d", table_bytes); msg.info("Average bytes per entry : %9.3f", avg_cost); msg.info("Average bucket size : %9.3f", summary.avg()); @@ -174,7 +190,28 @@ // The CompactHashtable implementation // +void SimpleCompactHashtable::init(address base_address, u4 entry_count, u4 bucket_count, u4* buckets, u4* entries) { + _bucket_count = bucket_count; + _entry_count = entry_count; + _base_address = base_address; + if (DynamicDumpSharedSpaces) { + _buckets = DynamicArchive::buffer_to_target(buckets); + _entries = DynamicArchive::buffer_to_target(entries); + } else { + _buckets = buckets; + _entries = entries; + } +} + +size_t SimpleCompactHashtable::calculate_header_size() { + // We have 5 fields. Each takes up sizeof(intptr_t). See WriteClosure::do_u4 + size_t bytes = sizeof(intptr_t) * 5; + return bytes; +} + void SimpleCompactHashtable::serialize_header(SerializeClosure* soc) { + // NOTE: if you change this function, you MUST change the number 5 in + // calculate_header_size() accordingly. soc->do_ptr((void**)&_base_address); soc->do_u4(&_entry_count); soc->do_u4(&_bucket_count); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/compactHashtable.hpp --- a/src/hotspot/share/classfile/compactHashtable.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/compactHashtable.hpp Fri May 17 08:29:55 2019 -0700 @@ -100,7 +100,7 @@ }; // class CompactHashtableWriter::Entry private: - int _num_entries; + int _num_entries_written; int _num_buckets; int _num_empty_buckets; int _num_value_only_buckets; @@ -112,7 +112,7 @@ public: // This is called at dump-time only - CompactHashtableWriter(int num_buckets, CompactHashtableStats* stats); + CompactHashtableWriter(int num_entries, CompactHashtableStats* stats); ~CompactHashtableWriter(); void add(unsigned int hash, u4 value); @@ -120,18 +120,16 @@ private: void allocate_table(); void dump_table(NumberSeq* summary); + static int calculate_num_buckets(int num_entries) { + int num_buckets = num_entries / SharedSymbolTableBucketSize; + // calculation of num_buckets can result in zero buckets, we need at least one + return (num_buckets < 1) ? 1 : num_buckets; + } public: void dump(SimpleCompactHashtable *cht, const char* table_name); - static int default_num_buckets(size_t num_entries) { - return default_num_buckets((int)num_entries); - } - static int default_num_buckets(int num_entries) { - int num_buckets = num_entries / SharedSymbolTableBucketSize; - // calculation of num_buckets can result in zero buckets, we need at least one - return (num_buckets < 1) ? 1 : num_buckets; - } + static size_t estimate_size(int num_entries); }; #endif // INCLUDE_CDS @@ -214,13 +212,7 @@ _entries = 0; } - void init(address base_address, u4 entry_count, u4 bucket_count, u4* buckets, u4* entries) { - _base_address = base_address; - _bucket_count = bucket_count; - _entry_count = entry_count; - _buckets = buckets; - _entries = entries; - } + void init(address base_address, u4 entry_count, u4 bucket_count, u4* buckets, u4* entries); // Read/Write the table's header from/to the CDS archive void serialize_header(SerializeClosure* soc) NOT_CDS_RETURN; @@ -228,6 +220,8 @@ inline bool empty() { return (_entry_count == 0); } + + static size_t calculate_header_size(); }; template < diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/dictionary.cpp --- a/src/hotspot/share/classfile/dictionary.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/dictionary.cpp Fri May 17 08:29:55 2019 -0700 @@ -246,7 +246,7 @@ // Used to scan and relocate the classes during CDS archive dump. void Dictionary::classes_do(MetaspaceClosure* it) { - assert(DumpSharedSpaces, "dump-time only"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "dump-time only"); for (int index = 0; index < table_size(); index++) { for (DictionaryEntry* probe = bucket(index); probe != NULL; @@ -312,7 +312,6 @@ } } - InstanceKlass* Dictionary::find_class(int index, unsigned int hash, Symbol* name) { assert_locked_or_safepoint(SystemDictionary_lock); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/klassFactory.cpp --- a/src/hotspot/share/classfile/klassFactory.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/klassFactory.cpp Fri May 17 08:29:55 2019 -0700 @@ -218,7 +218,7 @@ JFR_ONLY(ON_KLASS_CREATION(result, parser, THREAD);) #if INCLUDE_CDS - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { ClassLoader::record_result(result, stream, THREAD); } #endif // INCLUDE_CDS diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/sharedPathsMiscInfo.cpp --- a/src/hotspot/share/classfile/sharedPathsMiscInfo.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/sharedPathsMiscInfo.cpp Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -104,7 +104,7 @@ } } -bool SharedPathsMiscInfo::check() { +bool SharedPathsMiscInfo::check(bool is_static) { // The whole buffer must be 0 terminated so that we can use strlen and strcmp // without fear. _end_ptr -= sizeof(jint); @@ -116,9 +116,10 @@ } jshort cur_index = 0; - jshort max_cp_index = FileMapInfo::current_info()->header()->max_used_path_index(); - jshort module_paths_start_index = - FileMapInfo::current_info()->header()->app_module_paths_start_index(); + FileMapHeader* header = is_static ? FileMapInfo::current_info()->header() : + FileMapInfo::dynamic_info()->header(); + jshort max_cp_index = header->max_used_path_index(); + jshort module_paths_start_index = header->app_module_paths_start_index(); while (_cur_ptr < _end_ptr) { jint type; const char* path = _cur_ptr; @@ -136,7 +137,7 @@ } // skip checking the class path(s) which was not referenced during CDS dump if ((cur_index <= max_cp_index) || (cur_index >= module_paths_start_index)) { - if (!check(type, path)) { + if (!check(type, path, is_static)) { if (!PrintSharedArchiveAndExit) { return false; } @@ -171,7 +172,7 @@ return p; } -bool SharedPathsMiscInfo::check(jint type, const char* path) { +bool SharedPathsMiscInfo::check(jint type, const char* path, bool is_static) { assert(UseSharedSpaces, "runtime only"); switch (type) { case BOOT_PATH: @@ -196,7 +197,9 @@ char* rp = skip_first_path_entry(runtime_boot_path); char* dp = skip_first_path_entry(path); - bool relaxed_check = !FileMapInfo::current_info()->header()->has_platform_or_app_classes(); + bool relaxed_check = is_static ? + !FileMapInfo::current_info()->header()->has_platform_or_app_classes() : + !FileMapInfo::dynamic_info()->header()->has_platform_or_app_classes(); if (dp == NULL && rp == NULL) { break; // ok, both runtime and dump time boot paths have modules_images only } else if (dp == NULL && rp != NULL && relaxed_check) { diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/sharedPathsMiscInfo.hpp --- a/src/hotspot/share/classfile/sharedPathsMiscInfo.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/sharedPathsMiscInfo.hpp Fri May 17 08:29:55 2019 -0700 @@ -67,7 +67,7 @@ protected: static bool fail(const char* msg, const char* name = NULL); - bool check(jint type, const char* path); + bool check(jint type, const char* path, bool is_static); public: enum { @@ -162,7 +162,7 @@ } public: - bool check(); + bool check(bool is_static); }; #endif // SHARE_CLASSFILE_SHAREDPATHSMISCINFO_HPP diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/stringTable.cpp --- a/src/hotspot/share/classfile/stringTable.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/stringTable.cpp Fri May 17 08:29:55 2019 -0700 @@ -779,9 +779,7 @@ assert(HeapShared::is_heap_object_archiving_allowed(), "must be"); _shared_table.reset(); - int num_buckets = CompactHashtableWriter::default_num_buckets(_items_count); - CompactHashtableWriter writer(num_buckets, - &MetaspaceShared::stats()->string); + CompactHashtableWriter writer(_items_count, &MetaspaceShared::stats()->string); // Copy the interned strings into the "string space" within the java heap copy_shared_string_table(&writer); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/symbolTable.cpp --- a/src/hotspot/share/classfile/symbolTable.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/symbolTable.cpp Fri May 17 08:29:55 2019 -0700 @@ -28,6 +28,7 @@ #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "memory/allocation.inline.hpp" +#include "memory/dynamicArchive.hpp" #include "memory/metaspaceClosure.hpp" #include "memory/metaspaceShared.hpp" #include "memory/resourceArea.hpp" @@ -69,6 +70,11 @@ symbol_equals_compact_hashtable_entry > _shared_table; +static OffsetCompactHashtable< + const char*, Symbol*, + symbol_equals_compact_hashtable_entry +> _dynamic_shared_table; + // -------------------------------------------------------------------------- typedef ConcurrentHashTabledo_safepoint_scan(mpd); } @@ -287,19 +296,24 @@ return sym; } +#if INCLUDE_CDS Symbol* SymbolTable::lookup_shared(const char* name, int len, unsigned int hash) { + Symbol* sym = NULL; if (!_shared_table.empty()) { if (_alt_hash) { // hash_code parameter may use alternate hashing algorithm but the shared table // always uses the same original hash code. hash = hash_shared_symbol(name, len); } - return _shared_table.lookup(name, hash, len); - } else { - return NULL; + sym = _shared_table.lookup(name, hash, len); + if (sym == NULL && DynamicArchive::is_mapped()) { + sym = _dynamic_shared_table.lookup(name, hash, len); + } } + return sym; } +#endif Symbol* SymbolTable::lookup_common(const char* name, int len, unsigned int hash) { @@ -588,6 +602,9 @@ unsigned int fixed_hash = hash_shared_symbol((const char*)sym->bytes(), sym->utf8_length()); assert(fixed_hash == hash_symbol((const char*)sym->bytes(), sym->utf8_length(), false), "must not rehash during dumping"); + if (DynamicDumpSharedSpaces) { + sym = DynamicArchive::original_to_target(sym); + } _writer->add(fixed_hash, MetaspaceShared::object_delta_u4(sym)); return true; } @@ -598,30 +615,43 @@ _local_table->do_safepoint_scan(copy); } -void SymbolTable::write_to_archive() { - _shared_table.reset(); +size_t SymbolTable::estimate_size_for_archive() { + return CompactHashtableWriter::estimate_size(int(_items_count)); +} - int num_buckets = CompactHashtableWriter::default_num_buckets( - _items_count); - CompactHashtableWriter writer(num_buckets, +void SymbolTable::write_to_archive(bool is_static_archive) { + _shared_table.reset(); + _dynamic_shared_table.reset(); + + CompactHashtableWriter writer(int(_items_count), &MetaspaceShared::stats()->symbol); copy_shared_symbol_table(&writer); - writer.dump(&_shared_table, "symbol"); + if (is_static_archive) { + writer.dump(&_shared_table, "symbol"); - // Verify table is correct - Symbol* sym = vmSymbols::java_lang_Object(); - const char* name = (const char*)sym->bytes(); - int len = sym->utf8_length(); - unsigned int hash = hash_symbol(name, len, _alt_hash); - assert(sym == _shared_table.lookup(name, hash, len), "sanity"); + // Verify table is correct + Symbol* sym = vmSymbols::java_lang_Object(); + const char* name = (const char*)sym->bytes(); + int len = sym->utf8_length(); + unsigned int hash = hash_symbol(name, len, _alt_hash); + assert(sym == _shared_table.lookup(name, hash, len), "sanity"); + } else { + writer.dump(&_dynamic_shared_table, "symbol"); + } } -void SymbolTable::serialize_shared_table_header(SerializeClosure* soc) { - _shared_table.serialize_header(soc); - +void SymbolTable::serialize_shared_table_header(SerializeClosure* soc, + bool is_static_archive) { + OffsetCompactHashtable * table; + if (is_static_archive) { + table = &_shared_table; + } else { + table = &_dynamic_shared_table; + } + table->serialize_header(soc); if (soc->writing()) { // Sanity. Make sure we don't use the shared table at dump time - _shared_table.reset(); + table->reset(); } } #endif //INCLUDE_CDS diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/symbolTable.hpp --- a/src/hotspot/share/classfile/symbolTable.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/symbolTable.hpp Fri May 17 08:29:55 2019 -0700 @@ -137,7 +137,7 @@ const char** name, int* lengths, int* cp_indices, unsigned int* hashValues); - static Symbol* lookup_shared(const char* name, int len, unsigned int hash); + static Symbol* lookup_shared(const char* name, int len, unsigned int hash) NOT_CDS_RETURN_(NULL); static Symbol* lookup_dynamic(const char* name, int len, unsigned int hash); static Symbol* lookup_common(const char* name, int len, unsigned int hash); @@ -209,8 +209,10 @@ private: static void copy_shared_symbol_table(CompactHashtableWriter* ch_table); public: - static void write_to_archive() NOT_CDS_RETURN; - static void serialize_shared_table_header(SerializeClosure* soc) NOT_CDS_RETURN; + static size_t estimate_size_for_archive() NOT_CDS_RETURN_(0); + static void write_to_archive(bool is_static_archive = true) NOT_CDS_RETURN; + static void serialize_shared_table_header(SerializeClosure* soc, + bool is_static_archive = true) NOT_CDS_RETURN; static void metaspace_pointers_do(MetaspaceClosure* it); // Jcmd diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/systemDictionaryShared.cpp --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp Fri May 17 08:29:55 2019 -0700 @@ -44,6 +44,7 @@ #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "memory/dynamicArchive.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/objArrayOop.inline.hpp" @@ -61,9 +62,10 @@ objArrayOop SystemDictionaryShared::_shared_protection_domains = NULL; objArrayOop SystemDictionaryShared::_shared_jar_urls = NULL; objArrayOop SystemDictionaryShared::_shared_jar_manifests = NULL; -DEBUG_ONLY(bool SystemDictionaryShared::_checked_excluded_classes = false;) +DEBUG_ONLY(bool SystemDictionaryShared::_no_class_loading_should_happen = false;) class DumpTimeSharedClassInfo: public CHeapObj { + bool _excluded; public: struct DTConstraint { Symbol* _name; @@ -76,7 +78,6 @@ int _id; int _clsfile_size; int _clsfile_crc32; - bool _excluded; GrowableArray* _verifier_constraints; GrowableArray* _verifier_constraint_flags; @@ -115,6 +116,15 @@ } } } + + void set_excluded() { + _excluded = true; + } + + bool is_excluded() { + // _klass may become NULL due to DynamicArchiveBuilder::set_to_null + return _excluded || _klass == NULL; + } }; class DumpTimeSharedClassTable: public ResourceHashtable< @@ -131,8 +141,8 @@ DumpTimeSharedClassInfo* find_or_allocate_info_for(InstanceKlass* k) { DumpTimeSharedClassInfo* p = get(k); if (p == NULL) { - assert(!SystemDictionaryShared::checked_excluded_classes(), - "no new classes can be added after check_excluded_classes"); + assert(!SystemDictionaryShared::no_class_loading_should_happen(), + "no new classes can be loaded while dumping archive"); put(k, DumpTimeSharedClassInfo()); p = get(k); assert(p != NULL, "sanity"); @@ -146,16 +156,20 @@ public: CountClassByCategory(DumpTimeSharedClassTable* table) : _table(table) {} bool do_entry(InstanceKlass* k, DumpTimeSharedClassInfo& info) { - if (SystemDictionaryShared::is_builtin(k)) { - ++ _table->_builtin_count; - } else { - ++ _table->_unregistered_count; + if (!info.is_excluded()) { + if (info.is_builtin()) { + ++ _table->_builtin_count; + } else { + ++ _table->_unregistered_count; + } } return true; // keep on iterating } }; void update_counts() { + _builtin_count = 0; + _unregistered_count = 0; CountClassByCategory counter(this); iterate(&counter); } @@ -250,26 +264,36 @@ return (char*)(address(this) + verifier_constraint_flags_offset()); } + static u4 object_delta_u4(Symbol* sym) { + if (DynamicDumpSharedSpaces) { + sym = DynamicArchive::original_to_target(sym); + } + return MetaspaceShared::object_delta_u4(sym); + } + void init(DumpTimeSharedClassInfo& info) { _klass = info._klass; - _num_constraints = info.num_constraints(); if (!SystemDictionaryShared::is_builtin(_klass)) { CrcInfo* c = crc(); c->_clsfile_size = info._clsfile_size; c->_clsfile_crc32 = info._clsfile_crc32; } + _num_constraints = info.num_constraints(); if (_num_constraints > 0) { RTConstraint* constraints = verifier_constraints(); char* flags = verifier_constraint_flags(); int i; for (i = 0; i < _num_constraints; i++) { - constraints[i]._name = MetaspaceShared::object_delta_u4(info._verifier_constraints->at(i)._name); - constraints[i]._from_name = MetaspaceShared::object_delta_u4(info._verifier_constraints->at(i)._from_name); + constraints[i]._name = object_delta_u4(info._verifier_constraints->at(i)._name); + constraints[i]._from_name = object_delta_u4(info._verifier_constraints->at(i)._from_name); } for (i = 0; i < _num_constraints; i++) { flags[i] = info._verifier_constraint_flags->at(i); } } + if (DynamicDumpSharedSpaces) { + _klass = DynamicArchive::original_to_target(info._klass); + } } bool matches(int clsfile_size, int clsfile_crc32) const { @@ -307,7 +331,12 @@ return *info_pointer_addr(klass); } static void set_for(InstanceKlass* klass, RunTimeSharedClassInfo* record) { - *info_pointer_addr(klass) = record; + if (DynamicDumpSharedSpaces) { + klass = DynamicArchive::original_to_buffer(klass); + *info_pointer_addr(klass) = DynamicArchive::buffer_to_target(record); + } else { + *info_pointer_addr(klass) = record; + } } // Used by RunTimeSharedDictionary to implement OffsetCompactHashtable::EQUALS @@ -323,8 +352,12 @@ RunTimeSharedClassInfo::EQUALS> {}; static DumpTimeSharedClassTable* _dumptime_table = NULL; +// SystemDictionaries in the base layer static archive static RunTimeSharedDictionary _builtin_dictionary; static RunTimeSharedDictionary _unregistered_dictionary; +// SystemDictionaries in the top layer dynamic archive +static RunTimeSharedDictionary _dynamic_builtin_dictionary; +static RunTimeSharedDictionary _dynamic_unregistered_dictionary; oop SystemDictionaryShared::shared_protection_domain(int index) { return _shared_protection_domains->obj_at(index); @@ -710,6 +743,17 @@ return false; } +bool SystemDictionaryShared::has_platform_or_app_classes() { + if (FileMapInfo::current_info()->header()->has_platform_or_app_classes()) { + return true; + } + if (DynamicArchive::is_mapped() && + FileMapInfo::dynamic_info()->header()->has_platform_or_app_classes()) { + return true; + } + return false; +} + // The following stack shows how this code is reached: // // [0] SystemDictionaryShared::find_or_load_shared_class() @@ -747,7 +791,7 @@ Symbol* name, Handle class_loader, TRAPS) { InstanceKlass* k = NULL; if (UseSharedSpaces) { - if (!FileMapInfo::current_info()->header()->has_platform_or_app_classes()) { + if (!has_platform_or_app_classes()) { return NULL; } @@ -864,11 +908,17 @@ const RunTimeSharedClassInfo* record = find_record(&_unregistered_dictionary, class_name); if (record == NULL) { - return NULL; + if (DynamicArchive::is_mapped()) { + record = find_record(&_dynamic_unregistered_dictionary, class_name); + } + if (record == NULL) { + return NULL; + } } int clsfile_size = cfs->length(); int clsfile_crc32 = ClassLoader::crc32(0, (const char*)cfs->buffer(), cfs->length()); + if (!record->matches(clsfile_size, clsfile_crc32)) { return NULL; } @@ -971,6 +1021,7 @@ } DumpTimeSharedClassInfo* SystemDictionaryShared::find_or_allocate_info_for(InstanceKlass* k) { + MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); if (_dumptime_table == NULL) { _dumptime_table = new (ResourceObj::C_HEAP, mtClass)DumpTimeSharedClassTable(); } @@ -978,7 +1029,7 @@ } void SystemDictionaryShared::set_shared_class_misc_info(InstanceKlass* k, ClassFileStream* cfs) { - assert(DumpSharedSpaces, "only when dumping"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "only when dumping"); assert(!is_builtin(k), "must be unregistered class"); DumpTimeSharedClassInfo* info = find_or_allocate_info_for(k); info->_clsfile_size = cfs->length(); @@ -990,6 +1041,28 @@ } void SystemDictionaryShared::remove_dumptime_info(InstanceKlass* k) { + MutexLocker ml(DumpTimeTable_lock, Mutex::_no_safepoint_check_flag); + DumpTimeSharedClassInfo* p = _dumptime_table->get(k); + if (p == NULL) { + return; + } + if (p->_verifier_constraints != NULL) { + for (int i = 0; i < p->_verifier_constraints->length(); i++) { + DumpTimeSharedClassInfo::DTConstraint constraint = p->_verifier_constraints->at(i); + if (constraint._name != NULL ) { + constraint._name->decrement_refcount(); + } + if (constraint._from_name != NULL ) { + constraint._from_name->decrement_refcount(); + } + } + FREE_C_HEAP_ARRAY(DTConstraint, p->_verifier_constraints); + p->_verifier_constraints = NULL; + } + if (p->_verifier_constraint_flags != NULL) { + FREE_C_HEAP_ARRAY(char, p->_verifier_constraint_flags); + p->_verifier_constraint_flags = NULL; + } _dumptime_table->remove(k); } @@ -1010,9 +1083,11 @@ bool SystemDictionaryShared::should_be_excluded(InstanceKlass* k) { if (k->class_loader_data()->is_unsafe_anonymous()) { + warn_excluded(k, "Unsafe anonymous class"); return true; // unsafe anonymous classes are not archived, skip } if (k->is_in_error_state()) { + warn_excluded(k, "In error state"); return true; } if (k->shared_classpath_index() < 0 && is_builtin(k)) { @@ -1036,6 +1111,44 @@ warn_excluded(k, "JFR event class"); return true; } + if (k->init_state() < InstanceKlass::linked) { + // In static dumping, we will attempt to link all classes. Those that fail to link will + // be marked as in error state. + assert(DynamicDumpSharedSpaces, "must be"); + + // TODO -- rethink how this can be handled. + // We should try to link ik, however, we can't do it here because + // 1. We are at VM exit + // 2. linking a class may cause other classes to be loaded, which means + // a custom ClassLoader.loadClass() may be called, at a point where the + // class loader doesn't expect it. + warn_excluded(k, "Not linked"); + return true; + } + if (k->major_version() < 50 /*JAVA_6_VERSION*/) { + ResourceMark rm; + log_warning(cds)("Pre JDK 6 class not supported by CDS: %u.%u %s", + k->major_version(), k->minor_version(), k->name()->as_C_string()); + return true; + } + + InstanceKlass* super = k->java_super(); + if (super != NULL && should_be_excluded(super)) { + ResourceMark rm; + log_warning(cds)("Skipping %s: super class %s is excluded", k->name()->as_C_string(), super->name()->as_C_string()); + return true; + } + + Array* interfaces = k->local_interfaces(); + int len = interfaces->length(); + for (int i = 0; i < len; i++) { + InstanceKlass* intf = interfaces->at(i); + if (should_be_excluded(intf)) { + log_warning(cds)("Skipping %s: interface %s is excluded", k->name()->as_C_string(), intf->name()->as_C_string()); + return true; + } + } + return false; } @@ -1044,8 +1157,9 @@ ResourceMark rm; const char* name = k->name()->as_C_string(); DumpTimeSharedClassInfo* info = _dumptime_table->get(k); + assert(_no_class_loading_should_happen, "class loading must be disabled"); guarantee(info != NULL, "Class %s must be entered into _dumptime_table", name); - guarantee(!info->_excluded, "Should not attempt to archive excluded class %s", name); + guarantee(!info->is_excluded(), "Should not attempt to archive excluded class %s", name); if (is_builtin(k)) { guarantee(k->loader_type() != 0, "Class loader type must be set for BUILTIN class %s", name); @@ -1059,7 +1173,7 @@ public: bool do_entry(InstanceKlass* k, DumpTimeSharedClassInfo& info) { if (SystemDictionaryShared::should_be_excluded(k)) { - info._excluded = true; + info.set_excluded(); } return true; // keep on iterating } @@ -1068,13 +1182,13 @@ void SystemDictionaryShared::check_excluded_classes() { ExcludeDumpTimeSharedClasses excl; _dumptime_table->iterate(&excl); - DEBUG_ONLY(_checked_excluded_classes = true;) + _dumptime_table->update_counts(); } bool SystemDictionaryShared::is_excluded_class(InstanceKlass* k) { - assert(_checked_excluded_classes, "sanity"); - assert(DumpSharedSpaces, "only when dumping"); - return find_or_allocate_info_for(k)->_excluded; + assert(_no_class_loading_should_happen, "sanity"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "only when dumping"); + return find_or_allocate_info_for(k)->is_excluded(); } class IterateDumpTimeSharedClassTable : StackObj { @@ -1083,7 +1197,7 @@ IterateDumpTimeSharedClassTable(MetaspaceClosure* it) : _it(it) {} bool do_entry(InstanceKlass* k, DumpTimeSharedClassInfo& info) { - if (!info._excluded) { + if (!info.is_excluded()) { info.metaspace_pointers_do(_it); } return true; // keep on iterating @@ -1097,18 +1211,27 @@ bool SystemDictionaryShared::add_verification_constraint(InstanceKlass* k, Symbol* name, Symbol* from_name, bool from_field_is_protected, bool from_is_array, bool from_is_object) { - assert(DumpSharedSpaces, "called at dump time only"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "called at dump time only"); DumpTimeSharedClassInfo* info = find_or_allocate_info_for(k); info->add_verification_constraint(k, name, from_name, from_field_is_protected, from_is_array, from_is_object); - if (is_builtin(k)) { - // For builtin class loaders, we can try to complete the verification check at dump time, - // because we can resolve all the constraint classes. + + if (DynamicDumpSharedSpaces) { + // For dynamic dumping, we can resolve all the constraint classes for all class loaders during + // the initial run prior to creating the archive before vm exit. We will also perform verification + // check when running with the archive. return false; } else { - // For non-builtin class loaders, we cannot complete the verification check at dump time, - // because at dump time we don't know how to resolve classes for such loaders. - return true; + if (is_builtin(k)) { + // For builtin class loaders, we can try to complete the verification check at dump time, + // because we can resolve all the constraint classes. We will also perform verification check + // when running with the archive. + return false; + } else { + // For non-builtin class loaders, we cannot complete the verification check at dump time, + // because at dump time we don't know how to resolve classes for such loaders. + return true; + } } } @@ -1139,9 +1262,9 @@ if (log_is_enabled(Trace, cds, verification)) { ResourceMark rm; - log_trace(cds, verification)("add_verification_constraint: %s: %s must be subclass of %s", + log_trace(cds, verification)("add_verification_constraint: %s: %s must be subclass of %s [0x%x]", k->external_name(), from_name->as_klass_external_name(), - name->as_klass_external_name()); + name->as_klass_external_name(), c); } } @@ -1157,6 +1280,13 @@ Symbol* from_name = record->get_constraint_from_name(i); char c = record->get_constraint_flag(i); + if (log_is_enabled(Trace, cds, verification)) { + ResourceMark rm(THREAD); + log_trace(cds, verification)("check_verification_constraint: %s: %s must be subclass of %s [0x%x]", + klass->external_name(), from_name->as_klass_external_name(), + name->as_klass_external_name(), c); + } + bool from_field_is_protected = (c & SystemDictionaryShared::FROM_FIELD_IS_PROTECTED) ? true : false; bool from_is_array = (c & SystemDictionaryShared::FROM_IS_ARRAY) ? true : false; bool from_is_object = (c & SystemDictionaryShared::FROM_IS_OBJECT) ? true : false; @@ -1178,22 +1308,72 @@ } } +class EstimateSizeForArchive : StackObj { + size_t _shared_class_info_size; + int _num_builtin_klasses; + int _num_unregistered_klasses; + +public: + EstimateSizeForArchive() { + _shared_class_info_size = 0; + _num_builtin_klasses = 0; + _num_unregistered_klasses = 0; + } + + bool do_entry(InstanceKlass* k, DumpTimeSharedClassInfo& info) { + if (!info.is_excluded()) { + size_t byte_size = RunTimeSharedClassInfo::byte_size(info._klass, info.num_constraints()); + _shared_class_info_size += align_up(byte_size, BytesPerWord); + } + return true; // keep on iterating + } + + size_t total() { + return _shared_class_info_size; + } +}; + +size_t SystemDictionaryShared::estimate_size_for_archive() { + EstimateSizeForArchive est; + _dumptime_table->iterate(&est); + return est.total() + + CompactHashtableWriter::estimate_size(_dumptime_table->count_of(true)) + + CompactHashtableWriter::estimate_size(_dumptime_table->count_of(false)); +} + class CopySharedClassInfoToArchive : StackObj { CompactHashtableWriter* _writer; bool _is_builtin; public: - CopySharedClassInfoToArchive(CompactHashtableWriter* writer, bool is_builtin) + CopySharedClassInfoToArchive(CompactHashtableWriter* writer, + bool is_builtin, + bool is_static_archive) : _writer(writer), _is_builtin(is_builtin) {} bool do_entry(InstanceKlass* k, DumpTimeSharedClassInfo& info) { - if (!info._excluded && info.is_builtin() == _is_builtin) { + if (!info.is_excluded() && info.is_builtin() == _is_builtin) { size_t byte_size = RunTimeSharedClassInfo::byte_size(info._klass, info.num_constraints()); - RunTimeSharedClassInfo* record = - (RunTimeSharedClassInfo*)MetaspaceShared::read_only_space_alloc(byte_size); + RunTimeSharedClassInfo* record; + record = (RunTimeSharedClassInfo*)MetaspaceShared::read_only_space_alloc(byte_size); record->init(info); - unsigned int hash = primitive_hash(info._klass->name()); - _writer->add(hash, MetaspaceShared::object_delta_u4(record)); + unsigned int hash; + Symbol* name = info._klass->name(); + if (DynamicDumpSharedSpaces) { + name = DynamicArchive::original_to_target(name); + } + hash = primitive_hash(name); + u4 delta; + if (DynamicDumpSharedSpaces) { + delta = MetaspaceShared::object_delta_u4(DynamicArchive::buffer_to_target(record)); + } else { + delta = MetaspaceShared::object_delta_u4(record); + } + _writer->add(hash, delta); + if (log_is_enabled(Trace, cds, hashtables)) { + ResourceMark rm; + log_trace(cds,hashtables)("%s dictionary: %s", (_is_builtin ? "builtin" : "unregistered"), info._klass->external_name()); + } // Save this for quick runtime lookup of InstanceKlass* -> RunTimeSharedClassInfo* RunTimeSharedClassInfo::set_for(info._klass, record); @@ -1202,25 +1382,36 @@ } }; -void SystemDictionaryShared::write_dictionary(RunTimeSharedDictionary* dictionary, bool is_builtin) { +void SystemDictionaryShared::write_dictionary(RunTimeSharedDictionary* dictionary, + bool is_builtin, + bool is_static_archive) { CompactHashtableStats stats; dictionary->reset(); - int num_buckets = CompactHashtableWriter::default_num_buckets(_dumptime_table->count_of(is_builtin)); - CompactHashtableWriter writer(num_buckets, &stats); - CopySharedClassInfoToArchive copy(&writer, is_builtin); + CompactHashtableWriter writer(_dumptime_table->count_of(is_builtin), &stats); + CopySharedClassInfoToArchive copy(&writer, is_builtin, is_static_archive); _dumptime_table->iterate(©); writer.dump(dictionary, is_builtin ? "builtin dictionary" : "unregistered dictionary"); } -void SystemDictionaryShared::write_to_archive() { - _dumptime_table->update_counts(); - write_dictionary(&_builtin_dictionary, true); - write_dictionary(&_unregistered_dictionary, false); +void SystemDictionaryShared::write_to_archive(bool is_static_archive) { + if (is_static_archive) { + write_dictionary(&_builtin_dictionary, true); + write_dictionary(&_unregistered_dictionary, false); + } else { + write_dictionary(&_dynamic_builtin_dictionary, true); + write_dictionary(&_dynamic_unregistered_dictionary, false); + } } -void SystemDictionaryShared::serialize_dictionary_headers(SerializeClosure* soc) { - _builtin_dictionary.serialize_header(soc); - _unregistered_dictionary.serialize_header(soc); +void SystemDictionaryShared::serialize_dictionary_headers(SerializeClosure* soc, + bool is_static_archive) { + if (is_static_archive) { + _builtin_dictionary.serialize_header(soc); + _unregistered_dictionary.serialize_header(soc); + } else { + _dynamic_builtin_dictionary.serialize_header(soc); + _dynamic_unregistered_dictionary.serialize_header(soc); + } } const RunTimeSharedClassInfo* @@ -1237,9 +1428,16 @@ const RunTimeSharedClassInfo* record = find_record(&_builtin_dictionary, name); if (record) { return record->_klass; - } else { - return NULL; } + + if (DynamicArchive::is_mapped()) { + record = find_record(&_dynamic_builtin_dictionary, name); + if (record) { + return record->_klass; + } + } + + return NULL; } void SystemDictionaryShared::update_shared_entry(InstanceKlass* k, int id) { @@ -1266,14 +1464,31 @@ SharedDictionaryPrinter p(st); _builtin_dictionary.iterate(&p); _unregistered_dictionary.iterate(&p); + if (DynamicArchive::is_mapped()) { + _dynamic_builtin_dictionary.iterate(&p); + _unregistered_dictionary.iterate(&p); + } } } -void SystemDictionaryShared::print() { print_on(tty); } - void SystemDictionaryShared::print_table_statistics(outputStream* st) { if (UseSharedSpaces) { _builtin_dictionary.print_table_statistics(st, "Builtin Shared Dictionary"); _unregistered_dictionary.print_table_statistics(st, "Unregistered Shared Dictionary"); + if (DynamicArchive::is_mapped()) { + _dynamic_builtin_dictionary.print_table_statistics(st, "Dynamic Builtin Shared Dictionary"); + _dynamic_unregistered_dictionary.print_table_statistics(st, "Unregistered Shared Dictionary"); + } } } + +bool SystemDictionaryShared::empty_dumptime_table() { + if (_dumptime_table == NULL) { + return true; + } + _dumptime_table->update_counts(); + if (_dumptime_table->count_of(true) == 0 && _dumptime_table->count_of(false) == 0){ + return true; + } + return false; +} diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/systemDictionaryShared.hpp --- a/src/hotspot/share/classfile/systemDictionaryShared.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/systemDictionaryShared.hpp Fri May 17 08:29:55 2019 -0700 @@ -109,6 +109,7 @@ class RunTimeSharedDictionary; class SystemDictionaryShared: public SystemDictionary { + friend class ExcludeDumpTimeSharedClasses; public: enum { FROM_FIELD_IS_PROTECTED = 1 << 0, @@ -211,16 +212,21 @@ const ClassFileStream* cfs, TRAPS); static DumpTimeSharedClassInfo* find_or_allocate_info_for(InstanceKlass* k); - static void write_dictionary(RunTimeSharedDictionary* dictionary, bool is_builtin); + static void write_dictionary(RunTimeSharedDictionary* dictionary, + bool is_builtin, + bool is_static_archive = true); static bool is_jfr_event_class(InstanceKlass *k); static void warn_excluded(InstanceKlass* k, const char* reason); + static bool should_be_excluded(InstanceKlass* k); - DEBUG_ONLY(static bool _checked_excluded_classes;) + DEBUG_ONLY(static bool _no_class_loading_should_happen;) public: static InstanceKlass* find_builtin_class(Symbol* class_name); static const RunTimeSharedClassInfo* find_record(RunTimeSharedDictionary* dict, Symbol* name); + static bool has_platform_or_app_classes(); + // Called by PLATFORM/APP loader only static InstanceKlass* find_or_load_shared_class(Symbol* class_name, Handle class_loader, @@ -288,18 +294,34 @@ static bool is_builtin(InstanceKlass* k) { return (k->shared_classpath_index() != UNREGISTERED_INDEX); } - static bool should_be_excluded(InstanceKlass* k); static void check_excluded_classes(); static void validate_before_archiving(InstanceKlass* k); static bool is_excluded_class(InstanceKlass* k); static void dumptime_classes_do(class MetaspaceClosure* it); - static void write_to_archive(); - static void serialize_dictionary_headers(class SerializeClosure* soc); - static void print(); + static size_t estimate_size_for_archive(); + static void write_to_archive(bool is_static_archive = true); + static void serialize_dictionary_headers(class SerializeClosure* soc, + bool is_static_archive = true); + static void print() { return print_on(tty); } static void print_on(outputStream* st) NOT_CDS_RETURN; static void print_table_statistics(outputStream* st) NOT_CDS_RETURN; + static bool empty_dumptime_table() NOT_CDS_RETURN_(true); - DEBUG_ONLY(static bool checked_excluded_classes() {return _checked_excluded_classes;}) + DEBUG_ONLY(static bool no_class_loading_should_happen() {return _no_class_loading_should_happen;}) + +#ifdef ASSERT + class NoClassLoadingMark: public StackObj { + public: + NoClassLoadingMark() { + assert(!_no_class_loading_should_happen, "must not be nested"); + _no_class_loading_should_happen = true; + } + ~NoClassLoadingMark() { + _no_class_loading_should_happen = false; + } + }; +#endif + }; #endif // SHARE_CLASSFILE_SYSTEMDICTIONARYSHARED_HPP diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/classfile/verificationType.cpp --- a/src/hotspot/share/classfile/verificationType.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/classfile/verificationType.cpp Fri May 17 08:29:55 2019 -0700 @@ -94,12 +94,14 @@ return true; } - if (DumpSharedSpaces && SystemDictionaryShared::add_verification_constraint(klass, + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { + if (SystemDictionaryShared::add_verification_constraint(klass, name(), from.name(), from_field_is_protected, from.is_array(), from.is_object())) { - // If add_verification_constraint() returns true, the resolution/check should be - // delayed until runtime. - return true; + // If add_verification_constraint() returns true, the resolution/check should be + // delayed until runtime. + return true; + } } return resolve_and_check_assignability(klass, name(), from.name(), diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/include/cds.h --- a/src/hotspot/share/include/cds.h Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/include/cds.h Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, 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 @@ -35,6 +35,7 @@ #define NUM_CDS_REGIONS 9 #define CDS_ARCHIVE_MAGIC 0xf00baba2 +#define CDS_DYNAMIC_ARCHIVE_MAGIC 0xf00baba8 #define CURRENT_CDS_ARCHIVE_VERSION 5 #define INVALID_CDS_ARCHIVE_VERSION -1 diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/jfr/recorder/jfrRecorder.cpp --- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp Fri May 17 08:29:55 2019 -0700 @@ -168,7 +168,7 @@ static bool is_cds_dump_requested() { // we will not be able to launch recordings if a cds dump is being requested - if (DumpSharedSpaces && (JfrOptionSet::startup_recording_options() != NULL)) { + if ((DumpSharedSpaces || DynamicDumpSharedSpaces) && (JfrOptionSet::startup_recording_options() != NULL)) { warning("JFR will be disabled during CDS dumping"); teardown_startup_support(); return true; diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/logging/logTag.hpp --- a/src/hotspot/share/logging/logTag.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/logging/logTag.hpp Fri May 17 08:29:55 2019 -0700 @@ -66,6 +66,7 @@ LOG_TAG(defaultmethods) \ LOG_TAG(director) \ LOG_TAG(dump) \ + LOG_TAG(dynamic) \ LOG_TAG(ergo) \ LOG_TAG(event) \ LOG_TAG(exceptions) \ diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/allocation.hpp --- a/src/hotspot/share/memory/allocation.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/allocation.hpp Fri May 17 08:29:55 2019 -0700 @@ -254,10 +254,9 @@ // into a single contiguous memory block, so we can use these // two pointers to quickly determine if something is in the // shared metaspace. - // // When CDS is not enabled, both pointers are set to NULL. - static void* _shared_metaspace_base; // (inclusive) low address - static void* _shared_metaspace_top; // (exclusive) high address + static void* _shared_metaspace_base; // (inclusive) low address + static void* _shared_metaspace_top; // (exclusive) high address public: @@ -269,7 +268,8 @@ static bool is_shared(const MetaspaceObj* p) { // If no shared metaspace regions are mapped, _shared_metaspace_{base,top} will // both be NULL and all values of p will be rejected quickly. - return (((void*)p) < _shared_metaspace_top && ((void*)p) >= _shared_metaspace_base); + return (((void*)p) < _shared_metaspace_top && + ((void*)p) >= _shared_metaspace_base); } bool is_shared() const { return MetaspaceObj::is_shared(this); } @@ -279,6 +279,12 @@ _shared_metaspace_base = base; _shared_metaspace_top = top; } + + static void expand_shared_metaspace_range(void* top) { + assert(top >= _shared_metaspace_top, "must be"); + _shared_metaspace_top = top; + } + static void* shared_metaspace_base() { return _shared_metaspace_base; } static void* shared_metaspace_top() { return _shared_metaspace_top; } diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/dynamicArchive.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/dynamicArchive.cpp Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,1147 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "jvm.h" +#include "classfile/classLoaderData.inline.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" +#include "logging/log.hpp" +#include "memory/metadataFactory.hpp" +#include "memory/metaspace.hpp" +#include "memory/metaspaceClosure.hpp" +#include "memory/metaspaceShared.hpp" +#include "memory/resourceArea.hpp" +#include "memory/dynamicArchive.hpp" +#include "oops/compressedOops.hpp" +#include "oops/objArrayKlass.hpp" +#include "prims/jvmtiRedefineClasses.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/os.inline.hpp" +#include "runtime/sharedRuntime.hpp" +#include "runtime/vmThread.hpp" +#include "runtime/vmOperations.hpp" +#include "utilities/bitMap.inline.hpp" + +#ifndef O_BINARY // if defined (Win32) use binary files. +#define O_BINARY 0 // otherwise do nothing. +#endif + +class DynamicArchiveBuilder : ResourceObj { + CHeapBitMap _ptrmap; + static unsigned my_hash(const address& a) { + return primitive_hash
(a); + } + static bool my_equals(const address& a0, const address& a1) { + return primitive_equals
(a0, a1); + } + typedef ResourceHashtable< + address, address, + DynamicArchiveBuilder::my_hash, // solaris compiler doesn't like: primitive_hash
+ DynamicArchiveBuilder::my_equals, // solaris compiler doesn't like: primitive_equals
+ 16384, ResourceObj::C_HEAP> RelocationTable; + RelocationTable _new_loc_table; + + intx _buffer_to_target_delta; + + DumpRegion* _current_dump_space; + + static size_t reserve_alignment() { + return Metaspace::reserve_alignment(); + } + + static const int _total_dump_regions = 3; + int _num_dump_regions_used; + +public: + void mark_pointer(address* ptr_loc) { + if (is_in_buffer_space(ptr_loc)) { + size_t idx = pointer_delta(ptr_loc, _alloc_bottom, sizeof(address)); + _ptrmap.set_bit(idx); + } + } + + DumpRegion* current_dump_space() const { + return _current_dump_space; + } + + bool is_in_buffer_space(address p) const { + return (_alloc_bottom <= p && p < (address)current_dump_space()->top()); + } + + template bool is_in_target_space(T target_obj) const { + address buff_obj = address(target_obj) - _buffer_to_target_delta; + return is_in_buffer_space(buff_obj); + } + + template bool is_in_buffer_space(T obj) const { + return is_in_buffer_space(address(obj)); + } + + template T to_target_no_check(T obj) const { + return (T)(address(obj) + _buffer_to_target_delta); + } + + template T to_target(T obj) const { + assert(is_in_buffer_space(obj), "must be"); + return (T)(address(obj) + _buffer_to_target_delta); + } + + template T get_new_loc(T obj) { + address* pp = _new_loc_table.get((address)obj); + if (pp == NULL) { + // Excluded klasses are not copied + return NULL; + } else { + return (T)*pp; + } + } + + address get_new_loc(MetaspaceClosure::Ref* ref) { + return get_new_loc(ref->obj()); + } + + template bool has_new_loc(T obj) { + address* pp = _new_loc_table.get((address)obj); + return pp != NULL; + } + +protected: + enum FollowMode { + make_a_copy, point_to_it, set_to_null + }; + +public: + void copy(MetaspaceClosure::Ref* ref, bool read_only) { + int bytes = ref->size() * BytesPerWord; + address old_obj = ref->obj(); + address new_obj = copy_impl(ref, read_only, bytes); + + assert(new_obj != NULL, "must be"); + assert(new_obj != old_obj, "must be"); + bool isnew = _new_loc_table.put(old_obj, new_obj); + assert(isnew, "must be"); + } + + // Make a shallow copy of each eligible MetaspaceObj into the buffer. + class ShallowCopier: public UniqueMetaspaceClosure { + DynamicArchiveBuilder* _builder; + bool _read_only; + public: + ShallowCopier(DynamicArchiveBuilder* shuffler, bool read_only) + : _builder(shuffler), _read_only(read_only) {} + + virtual bool do_unique_ref(Ref* orig_obj, bool read_only) { + // This method gets called on each *original* object + // reachable from _builder->iterate_roots(). Each orig_obj is + // called exactly once. + FollowMode mode = _builder->follow_ref(orig_obj); + + if (mode == point_to_it) { + if (read_only == _read_only) { + log_debug(cds, dynamic)("ptr : " PTR_FORMAT " %s", p2i(orig_obj->obj()), + MetaspaceObj::type_name(orig_obj->msotype())); + address p = orig_obj->obj(); + bool isnew = _builder->_new_loc_table.put(p, p); + assert(isnew, "must be"); + } + return false; + } + + if (mode == set_to_null) { + log_debug(cds, dynamic)("nul : " PTR_FORMAT " %s", p2i(orig_obj->obj()), + MetaspaceObj::type_name(orig_obj->msotype())); + return false; + } + + if (read_only == _read_only) { + // Make a shallow copy of orig_obj in a buffer (maintained + // by copy_impl in a subclass of DynamicArchiveBuilder). + _builder->copy(orig_obj, read_only); + } + return true; + } + }; + + // Relocate all embedded pointer fields within a MetaspaceObj's shallow copy + class ShallowCopyEmbeddedRefRelocator: public UniqueMetaspaceClosure { + DynamicArchiveBuilder* _builder; + public: + ShallowCopyEmbeddedRefRelocator(DynamicArchiveBuilder* shuffler) + : _builder(shuffler) {} + + // This method gets called on each *original* object reachable + // from _builder->iterate_roots(). Each orig_obj is + // called exactly once. + virtual bool do_unique_ref(Ref* orig_ref, bool read_only) { + FollowMode mode = _builder->follow_ref(orig_ref); + + if (mode == point_to_it) { + // We did not make a copy of this object + // and we have nothing to update + assert(_builder->get_new_loc(orig_ref) == NULL || + _builder->get_new_loc(orig_ref) == orig_ref->obj(), "must be"); + return false; + } + + if (mode == set_to_null) { + // We did not make a copy of this object + // and we have nothing to update + assert(!_builder->has_new_loc(orig_ref->obj()), "must not be copied or pointed to"); + return false; + } + + // - orig_obj points to the original object. + // - new_obj points to the shallow copy (created by ShallowCopier) + // of orig_obj. new_obj is NULL if the orig_obj is excluded + address orig_obj = orig_ref->obj(); + address new_obj = _builder->get_new_loc(orig_ref); + + assert(new_obj != orig_obj, "must be"); +#ifdef ASSERT + if (new_obj == NULL) { + if (orig_ref->msotype() == MetaspaceObj::ClassType) { + Klass* k = (Klass*)orig_obj; + assert(k->is_instance_klass() && + SystemDictionaryShared::is_excluded_class(InstanceKlass::cast(k)), + "orig_obj must be excluded Class"); + } + } +#endif + + log_debug(cds, dynamic)("Relocating " PTR_FORMAT " %s", p2i(new_obj), + MetaspaceObj::type_name(orig_ref->msotype())); + if (new_obj != NULL) { + EmbeddedRefUpdater updater(_builder, orig_obj, new_obj); + orig_ref->metaspace_pointers_do(&updater); + } + + return true; // keep recursing until every object is visited exactly once. + } + }; + + class EmbeddedRefUpdater: public MetaspaceClosure { + DynamicArchiveBuilder* _builder; + address _orig_obj; + address _new_obj; + public: + EmbeddedRefUpdater(DynamicArchiveBuilder* shuffler, address orig_obj, address new_obj) : + _builder(shuffler), _orig_obj(orig_obj), _new_obj(new_obj) {} + + // This method gets called once for each pointer field F of orig_obj. + // We update new_obj->F to point to the new location of orig_obj->F. + // + // Example: Klass* 0x100 is copied to 0x400 + // Symbol* 0x200 is copied to 0x500 + // + // Let orig_obj == 0x100; and + // new_obj == 0x400; and + // ((Klass*)orig_obj)->_name == 0x200; + // Then this function effectively assigns + // ((Klass*)new_obj)->_name = 0x500; + virtual bool do_ref(Ref* ref, bool read_only) { + address new_pointee = NULL; + + if (ref->not_null()) { + address old_pointee = ref->obj(); + + FollowMode mode = _builder->follow_ref(ref); + if (mode == point_to_it) { + new_pointee = old_pointee; + } else if (mode == set_to_null) { + new_pointee = NULL; + } else { + new_pointee = _builder->get_new_loc(old_pointee); + } + } + + const char* kind = MetaspaceObj::type_name(ref->msotype()); + // offset of this field inside the original object + intx offset = (address)ref->addr() - _orig_obj; + _builder->update_pointer((address*)(_new_obj + offset), new_pointee, kind, offset); + + // We can't mark the pointer here, because DynamicArchiveBuilder::sort_methods + // may re-layout the [iv]tables, which would change the offset(s) in an InstanceKlass + // that would contain pointers. Therefore, we must mark the pointers after + // sort_methods(), using PointerMarker. + return false; // Do not recurse. + } + }; + + class ExternalRefUpdater: public MetaspaceClosure { + DynamicArchiveBuilder* _builder; + + public: + ExternalRefUpdater(DynamicArchiveBuilder* shuffler) : _builder(shuffler) {} + + virtual bool do_ref(Ref* ref, bool read_only) { + // ref is a pointer that lives OUTSIDE of the buffer, but points to an object inside the buffer + if (ref->not_null()) { + address new_loc = _builder->get_new_loc(ref); + const char* kind = MetaspaceObj::type_name(ref->msotype()); + _builder->update_pointer(ref->addr(), new_loc, kind, 0); + _builder->mark_pointer(ref->addr()); + } + return false; // Do not recurse. + } + }; + + class PointerMarker: public UniqueMetaspaceClosure { + DynamicArchiveBuilder* _builder; + + public: + PointerMarker(DynamicArchiveBuilder* shuffler) : _builder(shuffler) {} + + virtual bool do_unique_ref(Ref* ref, bool read_only) { + if (_builder->is_in_buffer_space(ref->obj())) { + EmbeddedRefMarker ref_marker(_builder); + ref->metaspace_pointers_do(&ref_marker); + return true; // keep recursing until every buffered object is visited exactly once. + } else { + return false; + } + } + }; + + class EmbeddedRefMarker: public MetaspaceClosure { + DynamicArchiveBuilder* _builder; + + public: + EmbeddedRefMarker(DynamicArchiveBuilder* shuffler) : _builder(shuffler) {} + virtual bool do_ref(Ref* ref, bool read_only) { + if (ref->not_null() && _builder->is_in_buffer_space(ref->obj())) { + _builder->mark_pointer(ref->addr()); + } + return false; // Do not recurse. + } + }; + + void update_pointer(address* addr, address value, const char* kind, uintx offset, bool is_mso_pointer=true) { + // Propagate the the mask bits to the new value -- see comments above MetaspaceClosure::obj() + if (is_mso_pointer) { + const uintx FLAG_MASK = 0x03; + uintx mask_bits = uintx(*addr) & FLAG_MASK; + value = (address)(uintx(value) | mask_bits); + } + + if (*addr != value) { + log_debug(cds, dynamic)("Update (%18s*) %3d [" PTR_FORMAT "] " PTR_FORMAT " -> " PTR_FORMAT, + kind, int(offset), p2i(addr), p2i(*addr), p2i(value)); + *addr = value; + } + } + +private: + GrowableArray* _symbols; // symbols to dump + GrowableArray* _klasses; // klasses to dump + + void append(InstanceKlass* k) { _klasses->append(k); } + void append(Symbol* s) { _symbols->append(s); } + + class GatherKlassesAndSymbols : public UniqueMetaspaceClosure { + DynamicArchiveBuilder* _builder; + bool _read_only; + + public: + GatherKlassesAndSymbols(DynamicArchiveBuilder* builder) + : _builder(builder) {} + + virtual bool do_unique_ref(Ref* ref, bool read_only) { + if (_builder->follow_ref(ref) != make_a_copy) { + return false; + } + if (ref->msotype() == MetaspaceObj::ClassType) { + Klass* klass = (Klass*)ref->obj(); + assert(klass->is_klass(), "must be"); + if (klass->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(klass); + assert(!SystemDictionaryShared::is_excluded_class(ik), "must be"); + _builder->append(ik); + _builder->_estimated_metsapceobj_bytes += BytesPerWord; // See RunTimeSharedClassInfo::get_for() + } + } else if (ref->msotype() == MetaspaceObj::SymbolType) { + _builder->append((Symbol*)ref->obj()); + } + + int bytes = ref->size() * BytesPerWord; + _builder->_estimated_metsapceobj_bytes += bytes; + + return true; + } + }; + + FollowMode follow_ref(MetaspaceClosure::Ref *ref) { + address obj = ref->obj(); + if (MetaspaceShared::is_in_shared_metaspace(obj)) { + // Don't dump existing shared metadata again. + return point_to_it; + } else if (ref->msotype() == MetaspaceObj::MethodDataType) { + return set_to_null; + } else { + if (ref->msotype() == MetaspaceObj::ClassType) { + Klass* klass = (Klass*)ref->obj(); + assert(klass->is_klass(), "must be"); + if (klass->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(klass); + if (SystemDictionaryShared::is_excluded_class(ik)) { + ResourceMark rm; + log_debug(cds, dynamic)("Skipping class (excluded): %s", klass->external_name()); + return set_to_null; + } + } else if (klass->is_array_klass()) { + // Don't support archiving of array klasses for now. + ResourceMark rm; + log_debug(cds, dynamic)("Skipping class (array): %s", klass->external_name()); + return set_to_null; + } + } + + return make_a_copy; + } + } + + address copy_impl(MetaspaceClosure::Ref* ref, bool read_only, int bytes) { + if (ref->msotype() == MetaspaceObj::ClassType) { + // Save a pointer immediate in front of an InstanceKlass, so + // we can do a quick lookup from InstanceKlass* -> RunTimeSharedClassInfo* + // without building another hashtable. See RunTimeSharedClassInfo::get_for() + // in systemDictionaryShared.cpp. + address obj = ref->obj(); + Klass* klass = (Klass*)obj; + if (klass->is_instance_klass()) { + SystemDictionaryShared::validate_before_archiving(InstanceKlass::cast(klass)); + current_dump_space()->allocate(sizeof(address), BytesPerWord); + } + } + address p = (address)current_dump_space()->allocate(bytes); + address obj = ref->obj(); + log_debug(cds, dynamic)("COPY: " PTR_FORMAT " ==> " PTR_FORMAT " %5d %s", + p2i(obj), p2i(p), bytes, + MetaspaceObj::type_name(ref->msotype())); + memcpy(p, obj, bytes); + + intptr_t* cloned_vtable = MetaspaceShared::fix_cpp_vtable_for_dynamic_archive(ref->msotype(), p); + if (cloned_vtable != NULL) { + update_pointer((address*)p, (address)cloned_vtable, "vtb", 0, /*is_mso_pointer*/false); + } + + return (address)p; + } + + DynamicArchiveHeader *_header; + address _alloc_bottom; + address _last_verified_top; + size_t _other_region_used_bytes; + + // Conservative estimate for number of bytes needed for: + size_t _estimated_metsapceobj_bytes; // all archived MetsapceObj's. + size_t _estimated_hashtable_bytes; // symbol table and dictionaries + size_t _estimated_trampoline_bytes; // method entry trampolines + + size_t estimate_archive_size(); + size_t estimate_trampoline_size(); + size_t estimate_class_file_size(); + address reserve_space_and_init_buffer_to_target_delta(); + void init_header(address addr); + void make_trampolines(); + void make_klasses_shareable(); + void sort_methods(InstanceKlass* ik) const; + void set_symbols_permanent(); + void relocate_buffer_to_target(); + void write_archive(char* read_only_tables_start); + + void init_first_dump_space(address reserved_bottom) { + address first_space_base = reserved_bottom; + DumpRegion* rw_space = MetaspaceShared::read_write_dump_space(); + MetaspaceShared::init_shared_dump_space(rw_space, first_space_base); + _current_dump_space = rw_space; + _last_verified_top = first_space_base; + _num_dump_regions_used = 1; + } + +public: + DynamicArchiveBuilder() { + _klasses = new (ResourceObj::C_HEAP, mtClass) GrowableArray(100, true, mtInternal); + _symbols = new (ResourceObj::C_HEAP, mtClass) GrowableArray(1000, true, mtInternal); + + _estimated_metsapceobj_bytes = 0; + _estimated_hashtable_bytes = 0; + _estimated_trampoline_bytes = 0; + + _num_dump_regions_used = 0; + } + + void start_dump_space(DumpRegion* next) { + address bottom = _last_verified_top; + address top = (address)(current_dump_space()->top()); + _other_region_used_bytes += size_t(top - bottom); + + MetaspaceShared::pack_dump_space(current_dump_space(), next, MetaspaceShared::shared_rs()); + _current_dump_space = next; + _num_dump_regions_used ++; + + _last_verified_top = (address)(current_dump_space()->top()); + } + + void verify_estimate_size(size_t estimate, const char* which) { + address bottom = _last_verified_top; + address top = (address)(current_dump_space()->top()); + size_t used = size_t(top - bottom) + _other_region_used_bytes; + int diff = int(estimate) - int(used); + + log_info(cds)("%s estimate = %lu used = %lu; diff = %d bytes", which, estimate, used, diff); + assert(diff >= 0, "Estimate is too small"); + + _last_verified_top = top; + _other_region_used_bytes = 0; + } + + // Do this before and after the archive dump to see if any corruption + // is caused by dynamic dumping. + void verify_universe(const char* info) { + if (VerifyBeforeExit) { + log_info(cds)("Verify %s", info); + HandleMark hm; + // Among other things, this ensures that Eden top is correct. + Universe::heap()->prepare_for_verify(); + Universe::verify(info); + } + } + + void doit() { + verify_universe("Before CDS dynamic dump"); + DEBUG_ONLY(SystemDictionaryShared::NoClassLoadingMark nclm); + SystemDictionaryShared::check_excluded_classes(); + + { + ResourceMark rm; + GatherKlassesAndSymbols gatherer(this); + + SystemDictionaryShared::dumptime_classes_do(&gatherer); + SymbolTable::metaspace_pointers_do(&gatherer); + FileMapInfo::metaspace_pointers_do(&gatherer); + + gatherer.finish(); + } + + // rw space starts ... + address reserved_bottom = reserve_space_and_init_buffer_to_target_delta(); + init_header(reserved_bottom); + + verify_estimate_size(sizeof(DynamicArchiveHeader), "header"); + + log_info(cds, dynamic)("Copying %d klasses and %d symbols", + _klasses->length(), _symbols->length()); + + { + assert(current_dump_space() == MetaspaceShared::read_write_dump_space(), + "Current dump space is not rw space"); + // shallow-copy RW objects, if necessary + ResourceMark rm; + ShallowCopier rw_copier(this, false); + iterate_roots(&rw_copier); + } + + // ro space starts ... + DumpRegion* ro_space = MetaspaceShared::read_only_dump_space(); + { + start_dump_space(ro_space); + + // shallow-copy RO objects, if necessary + ResourceMark rm; + ShallowCopier ro_copier(this, true); + iterate_roots(&ro_copier); + } + + size_t bitmap_size = pointer_delta(current_dump_space()->top(), + _alloc_bottom, sizeof(address)); + _ptrmap.initialize(bitmap_size); + + { + log_info(cds)("Relocating embedded pointers ... "); + ResourceMark rm; + ShallowCopyEmbeddedRefRelocator emb_reloc(this); + iterate_roots(&emb_reloc); + } + + { + log_info(cds)("Relocating external roots ... "); + ResourceMark rm; + ExternalRefUpdater ext_reloc(this); + iterate_roots(&ext_reloc); + } + + verify_estimate_size(_estimated_metsapceobj_bytes, "MetaspaceObjs"); + + char* read_only_tables_start; + { + set_symbols_permanent(); + + // Write the symbol table and system dictionaries to the RO space. + // Note that these tables still point to the *original* objects + // (because they were not processed by ExternalRefUpdater), so + // they would need to call DynamicArchive::original_to_target() to + // get the correct addresses. + assert(current_dump_space() == ro_space, "Must be RO space"); + SymbolTable::write_to_archive(false); + SystemDictionaryShared::write_to_archive(false); + + read_only_tables_start = ro_space->top(); + WriteClosure wc(ro_space); + SymbolTable::serialize_shared_table_header(&wc, false); + SystemDictionaryShared::serialize_dictionary_headers(&wc, false); + } + + verify_estimate_size(_estimated_hashtable_bytes, "Hashtables"); + + // mc space starts ... + { + start_dump_space(MetaspaceShared::misc_code_dump_space()); + make_trampolines(); + } + + verify_estimate_size(_estimated_trampoline_bytes, "Trampolines"); + + make_klasses_shareable(); + + { + log_info(cds)("Final relocation of pointers ... "); + ResourceMark rm; + PointerMarker marker(this); + iterate_roots(&marker); + relocate_buffer_to_target(); + } + + write_archive(read_only_tables_start); + + assert(_num_dump_regions_used == _total_dump_regions, "must be"); + verify_universe("After CDS dynamic dump"); + } + + void iterate_roots(MetaspaceClosure* it) { + int i; + int num_klasses = _klasses->length(); + for (i = 0; i < num_klasses; i++) { + it->push(&_klasses->at(i)); + } + + int num_symbols = _symbols->length(); + for (i = 0; i < num_symbols; i++) { + it->push(&_symbols->at(i)); + } + + _header->_shared_path_table.metaspace_pointers_do(it); + + // Do not call these again, as we have already collected all the classes and symbols + // that we want to archive. Also, these calls would corrupt the tables when + // ExternalRefUpdater is used. + // + // SystemDictionaryShared::dumptime_classes_do(it); + // SymbolTable::metaspace_pointers_do(it); + + it->finish(); + } +}; + +size_t DynamicArchiveBuilder::estimate_archive_size() { + // size of the symbol table and two dictionaries, plus the RunTimeSharedClassInfo's + _estimated_hashtable_bytes = 0; + _estimated_hashtable_bytes += SymbolTable::estimate_size_for_archive(); + _estimated_hashtable_bytes += SystemDictionaryShared::estimate_size_for_archive(); + + _estimated_trampoline_bytes = estimate_trampoline_size(); + + size_t total = 0; + + total += _estimated_metsapceobj_bytes; + total += _estimated_hashtable_bytes; + total += _estimated_trampoline_bytes; + + // allow fragmentation at the end of each dump region + total += _total_dump_regions * reserve_alignment(); + + return align_up(total, reserve_alignment()); +} + +address DynamicArchiveBuilder::reserve_space_and_init_buffer_to_target_delta() { + size_t total = estimate_archive_size(); + bool large_pages = false; // No large pages when dumping the CDS archive. + size_t increment = align_up(1*G, reserve_alignment()); + char* addr = (char*)align_up(CompressedKlassPointers::base() + MetaspaceSize + increment, + reserve_alignment()); + + ReservedSpace* rs = MetaspaceShared::reserve_shared_rs( + total, reserve_alignment(), large_pages, addr); + while (!rs->is_reserved() && (addr + increment > addr)) { + addr += increment; + rs = MetaspaceShared::reserve_shared_rs( + total, reserve_alignment(), large_pages, addr); + } + if (!rs->is_reserved()) { + log_error(cds, dynamic)("Failed to reserve %d bytes of output buffer.", (int)total); + vm_direct_exit(0); + } + + address buffer_base = (address)rs->base(); + log_info(cds, dynamic)("Reserved output buffer space at : " PTR_FORMAT " [%d bytes]", + p2i(buffer_base), (int)total); + + // At run time, we will mmap the dynamic archive at target_space_bottom. + // However, at dump time, we may not be able to write into the target_space, + // as it's occupied by dynamically loaded Klasses. So we allocate a buffer + // at an arbitrary location chosen by the OS. We will write all the dynamically + // archived classes into this buffer. At the final stage of dumping, we relocate + // all pointers that are inside the buffer_space to point to their (runtime) + // target location inside thetarget_space. + address target_space_bottom = + (address)align_up(MetaspaceShared::shared_metaspace_top(), reserve_alignment()); + _buffer_to_target_delta = intx(target_space_bottom) - intx(buffer_base); + + log_info(cds, dynamic)("Target archive space at : " PTR_FORMAT, p2i(target_space_bottom)); + log_info(cds, dynamic)("Buffer-space to target-space delta : " PTR_FORMAT, p2i((address)_buffer_to_target_delta)); + + return buffer_base; +} + +void DynamicArchiveBuilder::init_header(address reserved_bottom) { + _alloc_bottom = reserved_bottom; + _last_verified_top = reserved_bottom; + _other_region_used_bytes = 0; + + init_first_dump_space(reserved_bottom); + + FileMapInfo* mapinfo = new FileMapInfo(false); + _header = (DynamicArchiveHeader*)mapinfo->_header; + + Thread* THREAD = Thread::current(); + FileMapInfo* base_info = FileMapInfo::current_info(); + int* crc = _header->_base_archive_crc; + *crc++ = base_info->crc(); // base archive header crc + for (int i = 0; i < MetaspaceShared::n_regions; i++) { + *crc++ = base_info->space_crc(i); + } + _header->populate(base_info, os::vm_allocation_granularity()); +} + +size_t DynamicArchiveBuilder::estimate_trampoline_size() { + size_t total = 0; + size_t each_method_bytes = + align_up(SharedRuntime::trampoline_size(), BytesPerWord) + + align_up(sizeof(AdapterHandlerEntry*), BytesPerWord); + + for (int i = 0; i < _klasses->length(); i++) { + InstanceKlass* ik = _klasses->at(i); + Array* methods = ik->methods(); + total += each_method_bytes * methods->length(); + } + return total; +} + +void DynamicArchiveBuilder::make_trampolines() { + for (int i = 0; i < _klasses->length(); i++) { + InstanceKlass* ik = _klasses->at(i); + Array* methods = ik->methods(); + for (int j = 0; j < methods->length(); j++) { + Method* m = methods->at(j); + address c2i_entry_trampoline = + (address)MetaspaceShared::misc_code_space_alloc(SharedRuntime::trampoline_size()); + m->set_from_compiled_entry(to_target(c2i_entry_trampoline)); + AdapterHandlerEntry** adapter_trampoline = + (AdapterHandlerEntry**)MetaspaceShared::misc_code_space_alloc(sizeof(AdapterHandlerEntry*)); + *adapter_trampoline = NULL; + m->set_adapter_trampoline(to_target(adapter_trampoline)); + } + } +} + +void DynamicArchiveBuilder::make_klasses_shareable() { + int i, count = _klasses->length(); + + for (i = 0; i < count; i++) { + InstanceKlass* ik = _klasses->at(i); + sort_methods(ik); + } + + for (i = 0; i < count; i++) { + InstanceKlass* ik = _klasses->at(i); + ClassLoaderData *cld = ik->class_loader_data(); + if (cld->is_boot_class_loader_data()) { + ik->set_class_loader_type(ClassLoader::BOOT_LOADER); + } + else if (cld->is_platform_class_loader_data()) { + ik->set_class_loader_type(ClassLoader::PLATFORM_LOADER); + } + else if (cld->is_system_class_loader_data()) { + ik->set_class_loader_type(ClassLoader::APP_LOADER); + } + + MetaspaceShared::rewrite_nofast_bytecodes_and_calculate_fingerprints(ik); + ik->remove_unshareable_info(); + + assert(ik->array_klasses() == NULL, "sanity"); + + if (log_is_enabled(Debug, cds, dynamic)) { + ResourceMark rm; + log_debug(cds, dynamic)("klasses[%4i] = " PTR_FORMAT " %s", i, p2i(to_target(ik)), ik->external_name()); + } + } +} + +// The address order of the copied Symbols may be different than when the original +// klasses were created. Re-sort all the tables. See Method::sort_methods(). +void DynamicArchiveBuilder::sort_methods(InstanceKlass* ik) const { + assert(ik != NULL, "DynamicArchiveBuilder currently doesn't support dumping the base archive"); + if (MetaspaceShared::is_in_shared_metaspace(ik)) { + // We have reached a supertype that's already in the base archive + return; + } + + if (ik->java_mirror() == NULL) { + // NULL mirror means this class has already been visited and methods are already sorted + return; + } + ik->remove_java_mirror(); + + if (log_is_enabled(Debug, cds, dynamic)) { + ResourceMark rm; + log_debug(cds, dynamic)("sorting methods for " PTR_FORMAT " %s", p2i(to_target(ik)), ik->external_name()); + } + + // Make sure all supertypes have been sorted + sort_methods(ik->java_super()); + Array* interfaces = ik->local_interfaces(); + int len = interfaces->length(); + for (int i = 0; i < len; i++) { + sort_methods(interfaces->at(i)); + } + +#ifdef ASSERT + { + for (int m = 0; m < ik->methods()->length(); m++) { + Symbol* name = ik->methods()->at(m)->name(); + assert(MetaspaceShared::is_in_shared_metaspace(name) || is_in_buffer_space(name), "must be"); + } + } +#endif + + Thread* THREAD = Thread::current(); + Method::sort_methods(ik->methods()); + if (ik->default_methods() != NULL) { + Method::sort_methods(ik->default_methods(), /*set_idnums=*/false); + } + ik->vtable().initialize_vtable(true, THREAD); assert(!HAS_PENDING_EXCEPTION, "cannot fail"); + ik->itable().initialize_itable(true, THREAD); assert(!HAS_PENDING_EXCEPTION, "cannot fail"); +} + +void DynamicArchiveBuilder::set_symbols_permanent() { + int count = _symbols->length(); + for (int i=0; iat(i); + s->set_permanent(); + + if (log_is_enabled(Trace, cds, dynamic)) { + ResourceMark rm; + log_trace(cds, dynamic)("symbols[%4i] = " PTR_FORMAT " %s", i, p2i(to_target(s)), s->as_quoted_ascii()); + } + } +} + +class RelocateBufferToTarget: public BitMapClosure { + DynamicArchiveBuilder *_builder; + address* _buffer_bottom; + intx _buffer_to_target_delta; + public: + RelocateBufferToTarget(DynamicArchiveBuilder* builder, address* bottom, intx delta) : + _builder(builder), _buffer_bottom(bottom), _buffer_to_target_delta(delta) {} + + bool do_bit(size_t offset) { + address* p = _buffer_bottom + offset; + assert(_builder->is_in_buffer_space(p), "pointer must live in buffer space"); + + address old_ptr = *p; + if (_builder->is_in_buffer_space(old_ptr)) { + address new_ptr = old_ptr + _buffer_to_target_delta; + log_trace(cds, dynamic)("Final patch: @%6d [" PTR_FORMAT " -> " PTR_FORMAT "] " PTR_FORMAT " => " PTR_FORMAT, + (int)offset, p2i(p), p2i(_builder->to_target(p)), + p2i(old_ptr), p2i(new_ptr)); + *p = new_ptr; + } + + return true; // keep iterating + } +}; + + +void DynamicArchiveBuilder::relocate_buffer_to_target() { + RelocateBufferToTarget patcher(this, (address*)_alloc_bottom, _buffer_to_target_delta); + _ptrmap.iterate(&patcher); + + Array* table = _header->_shared_path_table.table(); + table = to_target(table); + _header->_shared_path_table.set_table(table); +} + +static void write_archive_info(FileMapInfo* dynamic_info, DynamicArchiveHeader *header) { + dynamic_info->write_header(); + dynamic_info->align_file_position(); + dynamic_info->write_region(MetaspaceShared::rw, + MetaspaceShared::read_write_dump_space()->base(), + MetaspaceShared::read_write_dump_space()->used(), + /*read_only=*/false,/*allow_exec=*/false); + dynamic_info->write_region(MetaspaceShared::ro, + MetaspaceShared::read_only_dump_space()->base(), + MetaspaceShared::read_only_dump_space()->used(), + /*read_only=*/true, /*allow_exec=*/false); + dynamic_info->write_region(MetaspaceShared::mc, + MetaspaceShared::misc_code_dump_space()->base(), + MetaspaceShared::misc_code_dump_space()->used(), + /*read_only=*/false,/*allow_exec=*/true); +} + +void DynamicArchiveBuilder::write_archive(char* read_only_tables_start) { + int num_klasses = _klasses->length(); + int num_symbols = _symbols->length(); + + _header->_read_only_tables_start = to_target(read_only_tables_start); + + FileMapInfo* dynamic_info = FileMapInfo::dynamic_info(); + assert(dynamic_info != NULL, "Sanity"); + + // Populate the file offsets, region crcs, etc. No data is written out. + write_archive_info(dynamic_info, _header); + + // the header will no longer change. Compute its crc. + dynamic_info->set_header_crc(dynamic_info->compute_header_crc()); + + // Now write the archived data including the file offsets. + const char* archive_name = Arguments::GetSharedDynamicArchivePath(); + dynamic_info->open_for_write(archive_name); + write_archive_info(dynamic_info, _header); + dynamic_info->close(); + + + address base = to_target(_alloc_bottom); + address top = address(current_dump_space()->top()) + _buffer_to_target_delta; + int file_size = int(top - base); + + log_info(cds, dynamic)("Written dynamic archive " PTR_FORMAT " - " PTR_FORMAT " [%d bytes header, %d bytes total]", + p2i(base), p2i(top), (int)_header->_header_size, file_size); + log_info(cds, dynamic)("%d klasses; %d symbols", num_klasses, num_symbols); +} + + +class VM_PopulateDynamicDumpSharedSpace: public VM_Operation { + DynamicArchiveBuilder* _builder; +public: + VM_PopulateDynamicDumpSharedSpace(DynamicArchiveBuilder* builder) : _builder(builder) {} + VMOp_Type type() const { return VMOp_PopulateDumpSharedSpace; } + void doit() { + ResourceMark rm; + if (SystemDictionaryShared::empty_dumptime_table()) { + log_warning(cds, dynamic)("There is no class to be included in the dynamic archive."); + return; + } + if (AllowArchivingWithJavaAgent) { + warning("This archive was created with AllowArchivingWithJavaAgent. It should be used " + "for testing purposes only and should not be used in a production environment"); + } + FileMapInfo::check_nonempty_dir_in_shared_path_table(); + + _builder->doit(); + } +}; + + +void DynamicArchive::dump() { + if (Arguments::GetSharedDynamicArchivePath() == NULL) { + log_warning(cds, dynamic)("SharedDynamicArchivePath is not specified"); + return; + } + + DynamicArchiveBuilder builder; + _builder = &builder; + VM_PopulateDynamicDumpSharedSpace op(&builder); + VMThread::execute(&op); + _builder = NULL; +} + +address DynamicArchive::original_to_buffer_impl(address orig_obj) { + assert(DynamicDumpSharedSpaces, "must be"); + address buff_obj = _builder->get_new_loc(orig_obj); + assert(buff_obj != NULL, "orig_obj must be used by the dynamic archive"); + assert(buff_obj != orig_obj, "call this only when you know orig_obj must be copied and not just referenced"); + assert(_builder->is_in_buffer_space(buff_obj), "must be"); + return buff_obj; +} + +address DynamicArchive::buffer_to_target_impl(address buff_obj) { + assert(DynamicDumpSharedSpaces, "must be"); + assert(_builder->is_in_buffer_space(buff_obj), "must be"); + return _builder->to_target(buff_obj); +} + +address DynamicArchive::original_to_target_impl(address orig_obj) { + assert(DynamicDumpSharedSpaces, "must be"); + if (MetaspaceShared::is_in_shared_metaspace(orig_obj)) { + // This happens when the top archive points to a Symbol* in the base archive. + return orig_obj; + } + address buff_obj = _builder->get_new_loc(orig_obj); + assert(buff_obj != NULL, "orig_obj must be used by the dynamic archive"); + if (buff_obj == orig_obj) { + // We are storing a pointer to an original object into the dynamic buffer. E.g., + // a Symbol* that used by both the base and top archives. + assert(MetaspaceShared::is_in_shared_metaspace(orig_obj), "must be"); + return orig_obj; + } else { + return _builder->to_target(buff_obj); + } +} + +uintx DynamicArchive::object_delta_uintx(void* buff_obj) { + assert(DynamicDumpSharedSpaces, "must be"); + address target_obj = _builder->to_target_no_check(address(buff_obj)); + assert(uintx(target_obj) >= SharedBaseAddress, "must be"); + return uintx(target_obj) - SharedBaseAddress; +} + +bool DynamicArchive::is_in_target_space(void *obj) { + assert(DynamicDumpSharedSpaces, "must be"); + return _builder->is_in_target_space(obj); +} + + +static DynamicArchiveHeader *_dynamic_header = NULL; +DynamicArchiveBuilder* DynamicArchive::_builder = NULL; + +void DynamicArchive::map_failed(FileMapInfo* mapinfo) { + if (mapinfo->_header != NULL) { + os::free(mapinfo->_header); + } + delete mapinfo; +} + +// Returns the top of the mapped address space +address DynamicArchive::map() { + assert(UseSharedSpaces, "Sanity"); + + // Create the dynamic archive map info + FileMapInfo* mapinfo; + const char* filename = Arguments::GetSharedDynamicArchivePath(); + struct stat st; + address result; + if ((filename != NULL) && (os::stat(filename, &st) == 0)) { + mapinfo = new FileMapInfo(false); + if (!mapinfo->open_for_read(filename)) { + result = NULL; + } + result = map_impl(mapinfo); + if (result == NULL) { + map_failed(mapinfo); + mapinfo->restore_shared_path_table(); + } + } else { + if (filename != NULL) { + log_warning(cds, dynamic)("specified dynamic archive doesn't exist: %s", filename); + } + result = NULL; + } + return result; +} + +address DynamicArchive::map_impl(FileMapInfo* mapinfo) { + + + // Read header + if (!mapinfo->initialize(false)) { + return NULL; + } + + _dynamic_header = (DynamicArchiveHeader*)mapinfo->header(); + + int regions[] = {MetaspaceShared::rw, + MetaspaceShared::ro, + MetaspaceShared::mc}; + + size_t len = sizeof(regions)/sizeof(int); + char* saved_base[] = {NULL, NULL, NULL}; + char* top = mapinfo->map_regions(regions, saved_base, len); + if (top == NULL) { + mapinfo->unmap_regions(regions, saved_base, len); + FileMapInfo::fail_continue("Unable to use dynamic archive. Failed map_region for using -Xshare:on."); + return NULL; + } + + if (!validate(mapinfo)) { + return NULL; + } + + if (_dynamic_header == NULL) { + return NULL; + } + + intptr_t* buffer = (intptr_t*)_dynamic_header->_read_only_tables_start; + ReadClosure rc(&buffer); + SymbolTable::serialize_shared_table_header(&rc, false); + SystemDictionaryShared::serialize_dictionary_headers(&rc, false); + + return (address)top; +} + +bool DynamicArchive::validate(FileMapInfo* dynamic_info) { + // Check if the recorded base archive matches with the current one + FileMapInfo* base_info = FileMapInfo::current_info(); + DynamicArchiveHeader* dynamic_header = (DynamicArchiveHeader*)dynamic_info->header(); + int* crc = dynamic_header->_base_archive_crc; + + // Check the header crc + if (*crc++ != base_info->crc()) { + FileMapInfo::fail_continue("Archive header checksum verification failed."); + return false; + } + + // Check each space's crc + for (int i = 0; i < MetaspaceShared::n_regions; i++) { + if (*crc++ != base_info->space_crc(i)) { + FileMapInfo::fail_continue("Archive region #%d checksum verification failed.", i); + return false; + } + } + + // Validate the dynamic archived shared path table, and set the global + // _shared_path_table to that. + if (!dynamic_info->validate_shared_path_table()) { + return false; + } + return true; +} + +bool DynamicArchive::is_mapped() { + return (_dynamic_header != NULL); +} + +void DynamicArchive::disable() { + _dynamic_header = NULL; +} diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/dynamicArchive.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/dynamicArchive.hpp Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_MEMORY_DYNAMICARCHIVE_HPP +#define SHARE_VM_MEMORY_DYNAMICARCHIVE_HPP + +#if INCLUDE_CDS + +#include "classfile/compactHashtable.hpp" +#include "memory/allocation.hpp" +#include "memory/filemap.hpp" +#include "memory/memRegion.hpp" +#include "memory/virtualspace.hpp" +#include "oops/oop.hpp" +#include "utilities/exceptions.hpp" +#include "utilities/macros.hpp" +#include "utilities/resourceHash.hpp" + +// We want to include all archive header information in the dynamic archive. +// This helps simplify the process if the base layer archive is missing at +// dynamic archiving time. +struct DynamicArchiveHeader : public FileMapHeader { + // crc for the base archive header and regions + int _base_archive_crc[MetaspaceShared::n_regions+1]; +}; + +class DynamicArchive : AllStatic { + static class DynamicArchiveBuilder* _builder; + static address original_to_target_impl(address orig_obj); + static address original_to_buffer_impl(address orig_obj); + static address buffer_to_target_impl(address buff_obj); + +public: + static void dump(); + + // obj is a copy of a MetaspaceObj, stored in the dumping buffer. + // + // The return value is the runtime targeted location of this object as + // mapped from the dynamic archive. + template static T buffer_to_target(T buff_obj) { + return (T)buffer_to_target_impl(address(buff_obj)); + } + + // obj is an original MetaspaceObj used by the JVM (e.g., a valid Symbol* in the + // SymbolTable). + // + // The return value is the runtime targeted location of this object as + // mapped from the dynamic archive. + template static T original_to_target(T obj) { + return (T)original_to_target_impl(address(obj)); + } + + // obj is an original MetaspaceObj use by the JVM (e.g., a valid Symbol* in the + // SymbolTable). + // + // The return value is the location of this object in the dump time + // buffer space + template static T original_to_buffer(T obj) { + return (T)original_to_buffer_impl(address(obj)); + } + + // Delta of this object from SharedBaseAddress + static uintx object_delta_uintx(void* buff_obj); + + // Does obj point to an address inside the runtime target space of the dynamic + // archive? + static bool is_in_target_space(void *obj); + + static address map(); + static bool is_mapped(); + static bool validate(FileMapInfo* dynamic_info); + static void disable(); +private: + static address map_impl(FileMapInfo* mapinfo); + static void map_failed(FileMapInfo* mapinfo); +}; +#endif // INCLUDE_CDS +#endif // SHARE_VM_MEMORY_DYNAMICARCHIVE_HPP diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/filemap.cpp --- a/src/hotspot/share/memory/filemap.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/filemap.cpp Fri May 17 08:29:55 2019 -0700 @@ -34,6 +34,7 @@ #include "logging/log.hpp" #include "logging/logStream.hpp" #include "logging/logMessage.hpp" +#include "memory/dynamicArchive.hpp" #include "memory/filemap.hpp" #include "memory/heapShared.inline.hpp" #include "memory/iterator.inline.hpp" @@ -42,6 +43,7 @@ #include "memory/metaspaceShared.hpp" #include "memory/oopFactory.hpp" #include "memory/universe.hpp" +#include "oops/compressedOops.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" @@ -101,7 +103,12 @@ void FileMapInfo::fail_continue(const char *msg, ...) { va_list ap; va_start(ap, msg); - MetaspaceShared::set_archive_loading_failed(); + if (_dynamic_archive_info == NULL) { + MetaspaceShared::set_archive_loading_failed(); + } else { + // _dynamic_archive_info has been setup after mapping the base archive + DynamicArchive::disable(); + } if (PrintSharedArchiveAndExit && _validating_shared_path_table) { // If we are doing PrintSharedArchiveAndExit and some of the classpath entries // do not validate, we can still continue "limping" to validate the remaining @@ -120,9 +127,15 @@ ls.vprint_cr(msg, ap); } } - UseSharedSpaces = false; - assert(current_info() != NULL, "singleton must be registered"); - current_info()->close(); + if (_dynamic_archive_info == NULL) { + UseSharedSpaces = false; + assert(current_info() != NULL, "singleton must be registered"); + current_info()->close(); + } else { + // We are failing when loading the top archive, but the base archive should + // continue to work. + log_warning(cds, dynamic)("Unable to use shared archive. The top archive failed to load: %s", _dynamic_archive_info->_full_path); + } } va_end(ap); } @@ -159,20 +172,36 @@ } } -FileMapInfo::FileMapInfo() { - assert(_current_info == NULL, "must be singleton"); // not thread safe - _current_info = this; +FileMapInfo::FileMapInfo(bool is_static) { memset((void*)this, 0, sizeof(FileMapInfo)); + _is_static = is_static; + size_t header_size; + if (is_static) { + assert(_current_info == NULL, "must be singleton"); // not thread safe + _current_info = this; + header_size = sizeof(FileMapHeader); + } else { + assert(_dynamic_archive_info == NULL, "must be singleton"); // not thread safe + _dynamic_archive_info = this; + header_size = sizeof(DynamicArchiveHeader); + } + _header = (FileMapHeader*)os::malloc(header_size, mtInternal); + memset((void*)_header, 0, header_size); + _header->_header_size = header_size; + _header->_version = INVALID_CDS_ARCHIVE_VERSION; + _header->_has_platform_or_app_classes = true; _file_offset = 0; _file_open = false; - _header = (FileMapHeader*)os::malloc(sizeof(FileMapHeader), mtInternal); - _header->_version = INVALID_CDS_ARCHIVE_VERSION; - _header->_has_platform_or_app_classes = true; } FileMapInfo::~FileMapInfo() { - assert(_current_info == this, "must be singleton"); // not thread safe - _current_info = NULL; + if (_is_static) { + assert(_current_info == this, "must be singleton"); // not thread safe + _current_info = NULL; + } else { + assert(_dynamic_archive_info == this, "must be singleton"); // not thread safe + _dynamic_archive_info = NULL; + } } void FileMapInfo::populate_header(size_t alignment) { @@ -180,7 +209,11 @@ } void FileMapHeader::populate(FileMapInfo* mapinfo, size_t alignment) { - _magic = CDS_ARCHIVE_MAGIC; + if (DynamicDumpSharedSpaces) { + _magic = CDS_DYNAMIC_ARCHIVE_MAGIC; + } else { + _magic = CDS_ARCHIVE_MAGIC; + } _version = CURRENT_CDS_ARCHIVE_VERSION; _alignment = alignment; _obj_alignment = ObjectAlignmentInBytes; @@ -191,9 +224,7 @@ _max_heap_size = MaxHeapSize; _narrow_klass_base = CompressedKlassPointers::base(); _narrow_klass_shift = CompressedKlassPointers::shift(); - _shared_path_table_size = mapinfo->_shared_path_table_size; _shared_path_table = mapinfo->_shared_path_table; - _shared_path_entry_size = mapinfo->_shared_path_entry_size; if (HeapShared::is_heap_object_archiving_allowed()) { _heap_reserved = Universe::heap()->reserved_region(); } @@ -208,6 +239,7 @@ ClassLoaderExt::finalize_shared_paths_misc_info(); _app_class_paths_start_index = ClassLoaderExt::app_class_paths_start_index(); _app_module_paths_start_index = ClassLoaderExt::app_module_paths_start_index(); + _num_module_paths = ClassLoader::num_module_path_entries(); _max_used_path_index = ClassLoaderExt::max_used_path_index(); _verify_local = BytecodeVerificationLocal; @@ -215,10 +247,13 @@ _has_platform_or_app_classes = ClassLoaderExt::has_platform_or_app_classes(); _shared_base_address = SharedBaseAddress; _allow_archiving_with_java_agent = AllowArchivingWithJavaAgent; + // the following 2 fields will be set in write_header for dynamic archive header + _base_archive_name_size = 0; + _base_archive_is_default = false; } void SharedClassPathEntry::init(const char* name, bool is_modules_image, TRAPS) { - assert(DumpSharedSpaces, "dump time only"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "dump time only"); _timestamp = 0; _filesize = 0; @@ -297,6 +332,7 @@ // If PrintSharedArchiveAndExit is enabled, don't report failure to the // caller. Please see above comments for more details. ok = true; + MetaspaceShared::set_archive_loading_failed(); } return ok; } @@ -306,8 +342,27 @@ it->push(&_manifest); } +void SharedPathTable::metaspace_pointers_do(MetaspaceClosure* it) { + it->push(&_table); + for (int i=0; i<_size; i++) { + path_at(i)->metaspace_pointers_do(it); + } +} + +void SharedPathTable::dumptime_init(ClassLoaderData* loader_data, Thread* THREAD) { + size_t entry_size = sizeof(SharedClassPathEntry); + int num_boot_classpath_entries = ClassLoader::num_boot_classpath_entries(); + int num_app_classpath_entries = ClassLoader::num_app_classpath_entries(); + int num_module_path_entries = ClassLoader::num_module_path_entries(); + int num_entries = num_boot_classpath_entries + num_app_classpath_entries + num_module_path_entries; + size_t bytes = entry_size * num_entries; + + _table = MetadataFactory::new_array(loader_data, (int)(bytes + 7 / 8), THREAD); + _size = num_entries; +} + void FileMapInfo::allocate_shared_path_table() { - assert(DumpSharedSpaces, "Sanity"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "Sanity"); Thread* THREAD = Thread::current(); ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data(); @@ -316,16 +371,7 @@ assert(jrt != NULL, "No modular java runtime image present when allocating the CDS classpath entry table"); - size_t entry_size = sizeof(SharedClassPathEntry); // assert ( should be 8 byte aligned??) - int num_boot_classpath_entries = ClassLoader::num_boot_classpath_entries(); - int num_app_classpath_entries = ClassLoader::num_app_classpath_entries(); - int num_module_path_entries = ClassLoader::num_module_path_entries(); - int num_entries = num_boot_classpath_entries + num_app_classpath_entries + num_module_path_entries; - size_t bytes = entry_size * num_entries; - - _shared_path_table = MetadataFactory::new_array(loader_data, (int)(bytes + 7 / 8), THREAD); - _shared_path_table_size = num_entries; - _shared_path_entry_size = entry_size; + _shared_path_table.dumptime_init(loader_data, THREAD); // 1. boot class path int i = 0; @@ -343,7 +389,7 @@ cpe = ClassLoader::get_next_boot_classpath_entry(cpe); i++; } - assert(i == num_boot_classpath_entries, + assert(i == ClassLoader::num_boot_classpath_entries(), "number of boot class path entry mismatch"); // 2. app class path @@ -369,15 +415,15 @@ mpe = mpe->next(); i++; } - assert(i == num_entries, "number of shared path entry mismatch"); + assert(i == _shared_path_table.size(), "number of shared path entry mismatch"); } void FileMapInfo::check_nonempty_dir_in_shared_path_table() { - assert(DumpSharedSpaces, "dump time only"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "dump time only"); bool has_nonempty_dir = false; - int last = _shared_path_table_size - 1; + int last = _shared_path_table.size() - 1; if (last > ClassLoaderExt::max_used_path_index()) { // no need to check any path beyond max_used_path_index last = ClassLoaderExt::max_used_path_index(); @@ -479,9 +525,29 @@ assert(UseSharedSpaces, "runtime only"); _validating_shared_path_table = true; + + // Load the shared path table info from the archive header _shared_path_table = _header->_shared_path_table; - _shared_path_entry_size = _header->_shared_path_entry_size; - _shared_path_table_size = _header->_shared_path_table_size; + if (DynamicDumpSharedSpaces) { + // Only support dynamic dumping with the usage of the default CDS archive + // or a simple base archive. + // If the base layer archive contains additional path component besides + // the runtime image and the -cp, dynamic dumping is disabled. + // + // When dynamic archiving is enabled, the _shared_path_table is overwritten + // to include the application path and stored in the top layer archive. + assert(shared_path(0)->is_modules_image(), "first shared_path must be the modules image"); + if (_header->_app_class_paths_start_index > 1) { + DynamicDumpSharedSpaces = false; + warning( + "Dynamic archiving is disabled because base layer archive has appended boot classpath"); + } + if (_header->_num_module_paths > 0) { + DynamicDumpSharedSpaces = false; + warning( + "Dynamic archiving is disabled because base layer archive has module path"); + } + } int module_paths_start_index = _header->_app_module_paths_start_index; @@ -491,14 +557,18 @@ if (shared_path(i)->validate()) { log_info(class, path)("ok"); } else { - assert(!UseSharedSpaces, "UseSharedSpaces should be disabled"); + if (_dynamic_archive_info != NULL && _dynamic_archive_info->_is_static) { + assert(!UseSharedSpaces, "UseSharedSpaces should be disabled"); + } return false; } } else if (i >= module_paths_start_index) { if (shared_path(i)->validate(false /* not a class path entry */)) { log_info(class, path)("ok"); } else { - assert(!UseSharedSpaces, "UseSharedSpaces should be disabled"); + if (_dynamic_archive_info != NULL && _dynamic_archive_info->_is_static) { + assert(!UseSharedSpaces, "UseSharedSpaces should be disabled"); + } return false; } } @@ -510,18 +580,151 @@ if (_classpath_entries_for_jvmti != NULL) { os::free(_classpath_entries_for_jvmti); } - size_t sz = sizeof(ClassPathEntry*) * _shared_path_table_size; + size_t sz = sizeof(ClassPathEntry*) * get_number_of_shared_paths(); _classpath_entries_for_jvmti = (ClassPathEntry**)os::malloc(sz, mtClass); - memset(_classpath_entries_for_jvmti, 0, sz); + memset((void*)_classpath_entries_for_jvmti, 0, sz); #endif return true; } +bool FileMapInfo::same_files(const char* file1, const char* file2) { + if (strcmp(file1, file2) == 0) { + return true; + } + + bool is_same = false; + // if the two paths diff only in case + struct stat st1; + struct stat st2; + int ret1; + int ret2; + ret1 = os::stat(file1, &st1); + ret2 = os::stat(file2, &st2); + if (ret1 < 0 || ret2 < 0) { + // one of the files is invalid. So they are not the same. + is_same = false; + } else if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) { + // different files + is_same = false; +#ifndef _WINDOWS + } else if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) { + // same files + is_same = true; +#else + } else if ((st1.st_size == st2.st_size) && (st1.st_ctime == st2.st_ctime) && + (st1.st_mtime == st2.st_mtime)) { + // same files + is_same = true; +#endif + } + return is_same; +} + +bool FileMapInfo::check_archive(const char* archive_name, bool is_static) { + int fd = os::open(archive_name, O_RDONLY | O_BINARY, 0); + if (fd < 0) { + // do not vm_exit_during_initialization here because Arguments::init_shared_archive_paths() + // requires a shared archive name. The open_for_read() function will log a message regarding + // failure in opening a shared archive. + return false; + } + + size_t sz = is_static ? sizeof(FileMapHeader) : sizeof(DynamicArchiveHeader); + void* header = os::malloc(sz, mtInternal); + memset(header, 0, sz); + size_t n = os::read(fd, header, (unsigned int)sz); + if (n != sz) { + os::free(header); + os::close(fd); + vm_exit_during_initialization("Unable to read header from shared archive", archive_name); + return false; + } + if (is_static) { + FileMapHeader* static_header = (FileMapHeader*)header; + if (static_header->_magic != CDS_ARCHIVE_MAGIC) { + os::free(header); + os::close(fd); + vm_exit_during_initialization("Not a base shared archive", archive_name); + return false; + } + } else { + DynamicArchiveHeader* dynamic_header = (DynamicArchiveHeader*)header; + if (dynamic_header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) { + os::free(header); + os::close(fd); + vm_exit_during_initialization("Not a top shared archive", archive_name); + return false; + } + } + os::free(header); + os::close(fd); + return true; +} + +bool FileMapInfo::get_base_archive_name_from_header(const char* archive_name, + int* size, char** base_archive_name) { + int fd = os::open(archive_name, O_RDONLY | O_BINARY, 0); + if (fd < 0) { + *size = 0; + return false; + } + + // read the header as a dynamic archive header + size_t sz = sizeof(DynamicArchiveHeader); + DynamicArchiveHeader* dynamic_header = (DynamicArchiveHeader*)os::malloc(sz, mtInternal); + size_t n = os::read(fd, dynamic_header, (unsigned int)sz); + if (n != sz) { + fail_continue("Unable to read the file header."); + os::free(dynamic_header); + os::close(fd); + return false; + } + if (dynamic_header->_magic != CDS_DYNAMIC_ARCHIVE_MAGIC) { + // Not a dynamic header, no need to proceed further. + *size = 0; + os::free(dynamic_header); + os::close(fd); + return false; + } + if (dynamic_header->_base_archive_is_default) { + *base_archive_name = Arguments::get_default_shared_archive_path(); + } else { + // skip over the _paths_misc_info + sz = dynamic_header->_paths_misc_info_size; + lseek(fd, (long)sz, SEEK_CUR); + // read the base archive name + size_t name_size = dynamic_header->_base_archive_name_size; + if (name_size == 0) { + os::free(dynamic_header); + os::close(fd); + return false; + } + *base_archive_name = NEW_C_HEAP_ARRAY(char, name_size, mtInternal); + n = os::read(fd, *base_archive_name, (unsigned int)name_size); + if (n != name_size) { + fail_continue("Unable to read the base archive name from the header."); + FREE_C_HEAP_ARRAY(char, *base_archive_name); + *base_archive_name = NULL; + os::free(dynamic_header); + os::close(fd); + return false; + } + } + + os::free(dynamic_header); + os::close(fd); + return true; +} + +void FileMapInfo::restore_shared_path_table() { + _shared_path_table = _current_info->_header->_shared_path_table; +} + // Read the FileMapInfo information from the file. -bool FileMapInfo::init_from_file(int fd) { - size_t sz = sizeof(FileMapHeader); +bool FileMapInfo::init_from_file(int fd, bool is_static) { + size_t sz = is_static ? sizeof(FileMapHeader) : sizeof(DynamicArchiveHeader); size_t n = os::read(fd, _header, (unsigned int)sz); if (n != sz) { fail_continue("Unable to read the file header."); @@ -531,14 +734,10 @@ fail_continue("The shared archive file has the wrong version."); return false; } - _file_offset = (long)n; + _file_offset = n; size_t info_size = _header->_paths_misc_info_size; - _paths_misc_info = NEW_C_HEAP_ARRAY_RETURN_NULL(char, info_size, mtClass); - if (_paths_misc_info == NULL) { - fail_continue("Unable to read the file header."); - return false; - } + _paths_misc_info = NEW_C_HEAP_ARRAY(char, info_size, mtClass); n = os::read(fd, _paths_misc_info, (unsigned int)info_size); if (n != info_size) { fail_continue("Unable to read the shared path info header."); @@ -546,29 +745,45 @@ _paths_misc_info = NULL; return false; } + _file_offset += n + _header->_base_archive_name_size; // accounts for the size of _base_archive_name - size_t len = lseek(fd, 0, SEEK_END); - CDSFileMapRegion* si = space_at(MetaspaceShared::last_valid_region); - // The last space might be empty - if (si->_file_offset > len || len - si->_file_offset < si->_used) { - fail_continue("The shared archive file has been truncated."); - return false; + if (is_static) { + if (_header->_magic != CDS_ARCHIVE_MAGIC) { + fail_continue("Incorrect static archive magic number"); + return false; + } + // just checking the last region is sufficient since the archive is written + // in sequential order + size_t len = lseek(fd, 0, SEEK_END); + CDSFileMapRegion* si = space_at(MetaspaceShared::last_valid_region); + // The last space might be empty + if (si->_file_offset > len || len - si->_file_offset < si->_used) { + fail_continue("The shared archive file has been truncated."); + return false; + } + + SharedBaseAddress = _header->_shared_base_address; } - _file_offset += (long)n; - SharedBaseAddress = _header->_shared_base_address; return true; } // Read the FileMapInfo information from the file. -bool FileMapInfo::open_for_read() { - _full_path = Arguments::GetSharedArchivePath(); +bool FileMapInfo::open_for_read(const char* path) { + if (_file_open) { + return true; + } + if (path == NULL) { + _full_path = Arguments::GetSharedArchivePath(); + } else { + _full_path = path; + } int fd = os::open(_full_path, O_RDONLY | O_BINARY, 0); if (fd < 0) { if (errno == ENOENT) { // Not locating the shared archive is ok. - fail_continue("Specified shared archive not found."); + fail_continue("Specified shared archive not found (%s).", _full_path); } else { fail_continue("Failed to open shared archive file (%s).", os::strerror(errno)); @@ -581,11 +796,14 @@ return true; } - // Write the FileMapInfo information to the file. -void FileMapInfo::open_for_write() { - _full_path = Arguments::GetSharedArchivePath(); +void FileMapInfo::open_for_write(const char* path) { + if (path == NULL) { + _full_path = Arguments::GetSharedArchivePath(); + } else { + _full_path = path; + } LogMessage(cds) msg; if (msg.is_info()) { msg.info("Dumping shared data to file: "); @@ -593,7 +811,7 @@ } #ifdef _WINDOWS // On Windows, need WRITE permission to remove the file. - chmod(_full_path, _S_IREAD | _S_IWRITE); + chmod(_full_path, _S_IREAD | _S_IWRITE); #endif // Use remove() to delete the existing file because, on Unix, this will @@ -617,40 +835,59 @@ _header->_paths_misc_info_size = info_size; - align_file_position(); - write_bytes(_header, sizeof(FileMapHeader)); + char* base_archive_name = NULL; + if (_header->_magic == CDS_DYNAMIC_ARCHIVE_MAGIC) { + base_archive_name = (char*)Arguments::GetSharedArchivePath(); + _header->_base_archive_name_size = (int)strlen(base_archive_name) + 1; + _header->_base_archive_is_default = FLAG_IS_DEFAULT(SharedArchiveFile); + } + + assert(is_file_position_aligned(), "must be"); + write_bytes(_header, _header->_header_size); write_bytes(ClassLoader::get_shared_paths_misc_info(), (size_t)info_size); + if (base_archive_name != NULL) { + write_bytes(base_archive_name, (size_t)_header->_base_archive_name_size); + } align_file_position(); } - // Dump region to file. - +// This is called twice for each region during archiving, once before +// the archive file is open (_file_open is false) and once after. void FileMapInfo::write_region(int region, char* base, size_t size, bool read_only, bool allow_exec) { + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "Dump time only"); + CDSFileMapRegion* si = space_at(region); + char* target_base = base; + if (DynamicDumpSharedSpaces) { + target_base = DynamicArchive::buffer_to_target(base); + } if (_file_open) { guarantee(si->_file_offset == _file_offset, "file offset mismatch."); log_info(cds)("Shared file region %d: " SIZE_FORMAT_HEX_W(08) " bytes, addr " INTPTR_FORMAT " file offset " SIZE_FORMAT_HEX_W(08), - region, size, p2i(base), _file_offset); + region, size, p2i(target_base), _file_offset); } else { si->_file_offset = _file_offset; } + if (HeapShared::is_heap_region(region)) { - assert((base - (char*)CompressedOops::base()) % HeapWordSize == 0, "Sanity"); - if (base != NULL) { - si->_addr._offset = (intx)CompressedOops::encode_not_null((oop)base); + assert((target_base - (char*)CompressedKlassPointers::base()) % HeapWordSize == 0, "Sanity"); + if (target_base != NULL) { + si->_addr._offset = (intx)CompressedOops::encode_not_null((oop)target_base); } else { si->_addr._offset = 0; } } else { - si->_addr._base = base; + si->_addr._base = target_base; } si->_used = size; si->_read_only = read_only; si->_allow_exec = allow_exec; + + // Use the current 'base' when computing the CRC value and writing out data si->_crc = ClassLoader::crc32(0, base, (jint)size); if (base != NULL) { write_bytes_aligned(base, size); @@ -734,8 +971,7 @@ if (_file_open) { size_t n = os::write(_fd, buffer, (unsigned int)nbytes); if (n != nbytes) { - // It is dangerous to leave the corrupted shared archive file around, - // close and remove the file. See bug 6372906. + // If the shared archive is corrupted, close it and remove it. close(); remove(_full_path); fail_stop("Unable to write to shared archive file."); @@ -744,6 +980,10 @@ _file_offset += nbytes; } +bool FileMapInfo::is_file_position_aligned() const { + return _file_offset == align_up(_file_offset, + os::vm_allocation_granularity()); +} // Align file position to an allocation unit boundary. @@ -843,6 +1083,30 @@ static const char* shared_region_name[] = { "MiscData", "ReadWrite", "ReadOnly", "MiscCode", "OptionalData", "String1", "String2", "OpenArchive1", "OpenArchive2" }; +char* FileMapInfo::map_regions(int regions[], char* saved_base[], size_t len) { + char* prev_top = NULL; + char* curr_base; + char* curr_top; + int i = 0; + for (i = 0; i < (int)len; i++) { + curr_base = map_region(regions[i], &curr_top); + if (curr_base == NULL) { + return NULL; + } + if (i > 0) { + // We require that mc->rw->ro->md->od to be laid out consecutively, with no + // gaps between them. That way, we can ensure that the OS won't be able to + // allocate any new memory spaces inside _shared_metaspace_{base,top}, which + // would mess up the simple comparision in MetaspaceShared::is_in_shared_metaspace(). + assert(curr_base == prev_top, "must be"); + } + log_info(cds)("Mapped region #%d at base %p top %p", regions[i], curr_base, curr_top); + saved_base[i] = curr_base; + prev_top = curr_top; + } + return curr_top; +} + char* FileMapInfo::map_region(int i, char** top_ret) { assert(!HeapShared::is_heap_region(i), "sanity"); CDSFileMapRegion* si = space_at(i); @@ -869,6 +1133,7 @@ si->_allow_exec); if (base == NULL || base != requested_addr) { fail_continue("Unable to map %s shared space at required address.", shared_region_name[i]); + _memory_mapping_failed = true; return NULL; } #ifdef _WINDOWS @@ -885,6 +1150,18 @@ return base; } +size_t FileMapInfo::read_bytes(void* buffer, size_t count) { + assert(_file_open, "Archive file is not open"); + size_t n = os::read(_fd, buffer, (unsigned int)count); + if (n != count) { + // Close the file if there's a problem reading it. + close(); + return 0; + } + _file_offset += count; + return count; +} + address FileMapInfo::decode_start_address(CDSFileMapRegion* spc, bool with_current_oop_encoding_mode) { if (with_current_oop_encoding_mode) { return (address)CompressedOops::decode_not_null(offset_of_space(spc)); @@ -1126,13 +1403,13 @@ p2i(addr), regions[i].byte_size()); return false; } - } - if (!verify_mapped_heap_regions(first, region_num)) { - // dealloc the regions from java heap - dealloc_archive_heap_regions(regions, region_num, is_open_archive); - log_info(cds)("UseSharedSpaces: mapped heap regions are corrupt"); - return false; + if (VerifySharedSpaces && !region_crc_check(addr, regions[i].byte_size(), si->_crc)) { + // dealloc the regions from java heap + dealloc_archive_heap_regions(regions, region_num, is_open_archive); + log_info(cds)("UseSharedSpaces: mapped heap regions are corrupt"); + return false; + } } // the shared heap data is mapped successfully @@ -1141,18 +1418,6 @@ return true; } -bool FileMapInfo::verify_mapped_heap_regions(int first, int num) { - assert(num > 0, "sanity"); - if (VerifySharedSpaces) { - for (int i = first; i < first + num; i++) { - if (!verify_region_checksum(i)) { - return false; - } - } - } - return true; -} - void FileMapInfo::patch_archived_heap_embedded_pointers() { if (!_heap_pointers_need_patching) { return; @@ -1205,6 +1470,15 @@ } #endif // INCLUDE_CDS_JAVA_HEAP +bool FileMapInfo::region_crc_check(char* buf, size_t size, int expected_crc) { + int crc = ClassLoader::crc32(0, buf, (jint)size); + if (crc != expected_crc) { + fail_continue("Checksum verification failed."); + return false; + } + return true; +} + bool FileMapInfo::verify_region_checksum(int i) { assert(VerifySharedSpaces, "sanity"); @@ -1213,19 +1487,16 @@ if (sz == 0) { return true; // no data } - if ((HeapShared::is_closed_archive_heap_region(i) && - !HeapShared::closed_archive_heap_region_mapped()) || - (HeapShared::is_open_archive_heap_region(i) && - !HeapShared::open_archive_heap_region_mapped())) { - return true; // archived heap data is not mapped + + return region_crc_check(region_addr(i), sz, space_at(i)->_crc); +} + +void FileMapInfo::unmap_regions(int regions[], char* saved_base[], size_t len) { + for (int i = 0; i < (int)len; i++) { + if (saved_base[i] != NULL) { + unmap_region(regions[i]); + } } - const char* buf = region_addr(i); - int crc = ClassLoader::crc32(0, buf, (jint)sz); - if (crc != space_at(i)->_crc) { - fail_continue("Checksum verification failed."); - return false; - } - return true; } // Unmap a memory region in the address space. @@ -1253,19 +1524,15 @@ } void FileMapInfo::metaspace_pointers_do(MetaspaceClosure* it) { - it->push(&_shared_path_table); - for (int i=0; i<_shared_path_table_size; i++) { - shared_path(i)->metaspace_pointers_do(it); - } + _shared_path_table.metaspace_pointers_do(it); } - FileMapInfo* FileMapInfo::_current_info = NULL; +FileMapInfo* FileMapInfo::_dynamic_archive_info = NULL; bool FileMapInfo::_heap_pointers_need_patching = false; -Array* FileMapInfo::_shared_path_table = NULL; -int FileMapInfo::_shared_path_table_size = 0; -size_t FileMapInfo::_shared_path_entry_size = 0x1234baad; +SharedPathTable FileMapInfo::_shared_path_table; bool FileMapInfo::_validating_shared_path_table = false; +bool FileMapInfo::_memory_mapping_failed = false; // Open the shared archive file, read and validate the header // information (version, boot classpath, etc.). If initialization @@ -1277,7 +1544,7 @@ // [1] validate_header() - done here. This checks the header, including _paths_misc_info. // [2] validate_shared_path_table - this is done later, because the table is in the RW // region of the archive, which is not mapped yet. -bool FileMapInfo::initialize() { +bool FileMapInfo::initialize(bool is_static) { assert(UseSharedSpaces, "UseSharedSpaces expected."); if (JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_early_class_hook_env()) { @@ -1293,8 +1560,8 @@ return false; } - init_from_file(_fd); - if (!validate_header()) { + init_from_file(_fd, is_static); + if (!validate_header(is_static)) { return false; } return true; @@ -1315,7 +1582,7 @@ char* start = (char*)this; // start computing from the field after _crc char* buf = (char*)&_crc + sizeof(_crc); - size_t sz = sizeof(FileMapHeader) - (buf - start); + size_t sz = _header_size - (buf - start); int crc = ClassLoader::crc32(0, buf, (jint)sz); return crc; } @@ -1336,7 +1603,7 @@ FileMapInfo::fail_continue("The shared archive file is the wrong version."); return false; } - if (_magic != CDS_ARCHIVE_MAGIC) { + if (_magic != CDS_ARCHIVE_MAGIC && _magic != CDS_DYNAMIC_ARCHIVE_MAGIC) { FileMapInfo::fail_continue("The shared archive file has a bad magic number."); return false; } @@ -1401,11 +1668,11 @@ return true; } -bool FileMapInfo::validate_header() { +bool FileMapInfo::validate_header(bool is_static) { bool status = _header->validate(); if (status) { - if (!ClassLoader::check_shared_paths_misc_info(_paths_misc_info, _header->_paths_misc_info_size)) { + if (!ClassLoader::check_shared_paths_misc_info(_paths_misc_info, _header->_paths_misc_info_size, is_static)) { if (!PrintSharedArchiveAndExit) { fail_continue("shared class paths mismatch (hint: enable -Xlog:class+path=info to diagnose the failure)"); status = false; @@ -1435,7 +1702,7 @@ // Unmap mapped regions of shared space. void FileMapInfo::stop_sharing_and_unmap(const char* msg) { - MetaspaceObj::set_shared_metaspace_range(NULL, NULL); + MetaspaceShared::set_shared_metaspace_range(NULL, NULL); FileMapInfo *map_info = FileMapInfo::current_info(); if (map_info) { @@ -1502,7 +1769,7 @@ ClassFileStream* FileMapInfo::open_stream_for_jvmti(InstanceKlass* ik, Handle class_loader, TRAPS) { int path_index = ik->shared_classpath_index(); assert(path_index >= 0, "should be called for shared built-in classes only"); - assert(path_index < (int)_shared_path_table_size, "sanity"); + assert(path_index < (int)get_number_of_shared_paths(), "sanity"); ClassPathEntry* cpe = get_classpath_entry_for_jvmti(path_index, CHECK_NULL); assert(cpe != NULL, "must be"); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/filemap.hpp --- a/src/hotspot/share/memory/filemap.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/filemap.hpp Fri May 17 08:29:55 2019 -0700 @@ -93,7 +93,32 @@ size_t _oopmap_size_in_bits; }; +class SharedPathTable { + Array* _table; + int _size; +public: + void dumptime_init(ClassLoaderData* loader_data, Thread* THREAD); + void metaspace_pointers_do(MetaspaceClosure* it); + + int size() { + return _size; + } + SharedClassPathEntry* path_at(int index) { + if (index < 0) { + return NULL; + } + assert(index < _size, "sanity"); + char* p = (char*)_table->data(); + p += sizeof(SharedClassPathEntry) * index; + return (SharedClassPathEntry*)p; + } + Array* table() {return _table;} + void set_table(Array* table) {_table = table;} + +}; + struct FileMapHeader : public CDSFileMapHeaderBase { + size_t _header_size; size_t _alignment; // how shared archive should be aligned int _obj_alignment; // value of ObjectAlignmentInBytes address _narrow_oop_base; // compressed oop encoding base @@ -110,12 +135,16 @@ size_t _core_spaces_size; // number of bytes allocated by the core spaces // (mc, md, ro, rw and od). MemRegion _heap_reserved; // reserved region for the entire heap at dump time. + bool _base_archive_is_default; // indicates if the base archive is the system default one // The following fields are all sanity checks for whether this archive // will function correctly with this JVM and the bootclasspath it's // invoked with. char _jvm_ident[JVM_IDENT_MAX]; // identifier for jvm + // size of the base archive name including NULL terminator + int _base_archive_name_size; + // The _paths_misc_info is a variable-size structure that records "miscellaneous" // information during dumping. It is generated and validated by the // SharedPathsMiscInfo class. See SharedPathsMiscInfo.hpp for @@ -140,12 +169,11 @@ // FIXME -- if JAR files in the tail of the list were specified but not used during dumping, // they should be removed from this table, to save space and to avoid spurious // loading failures during runtime. - int _shared_path_table_size; - size_t _shared_path_entry_size; - Array* _shared_path_table; + SharedPathTable _shared_path_table; jshort _app_class_paths_start_index; // Index of first app classpath entry jshort _app_module_paths_start_index; // Index of first module path entry + jshort _num_module_paths; // number of module path entries jshort _max_used_path_index; // max path index referenced during CDS dump bool _verify_local; // BytecodeVerificationLocal setting bool _verify_remote; // BytecodeVerificationRemote setting @@ -161,13 +189,14 @@ jshort app_module_paths_start_index() { return _app_module_paths_start_index; } bool validate(); - void populate(FileMapInfo* info, size_t alignment); int compute_crc(); CDSFileMapRegion* space_at(int i) { assert(i >= 0 && i < NUM_CDS_REGIONS, "invalid region"); return &_space[i]; } +public: + void populate(FileMapInfo* info, size_t alignment); }; class FileMapInfo : public CHeapObj { @@ -176,14 +205,14 @@ friend class VMStructs; friend struct FileMapHeader; + bool _is_static; bool _file_open; int _fd; size_t _file_offset; private: - static Array* _shared_path_table; - static int _shared_path_table_size; - static size_t _shared_path_entry_size; + // TODO: Probably change the following to be non-static + static SharedPathTable _shared_path_table; static bool _validating_shared_path_table; // FileMapHeader describes the shared space data in the file to be @@ -202,24 +231,31 @@ const char* _full_path; char* _paths_misc_info; + char* _base_archive_name; static FileMapInfo* _current_info; + static FileMapInfo* _dynamic_archive_info; static bool _heap_pointers_need_patching; - - bool init_from_file(int fd); - void align_file_position(); - bool validate_header_impl(); + static bool _memory_mapping_failed; + static bool get_base_archive_name_from_header(const char* archive_name, + int* size, char** base_archive_name); + static bool check_archive(const char* archive_name, bool is_static); + static bool same_files(const char* file1, const char* file2); + void restore_shared_path_table(); + bool init_from_file(int fd, bool is_static); static void metaspace_pointers_do(MetaspaceClosure* it); public: - FileMapInfo(); + FileMapInfo(bool is_static); ~FileMapInfo(); int compute_header_crc() { return _header->compute_crc(); } void set_header_crc(int crc) { _header->_crc = crc; } + int space_crc(int i) { return space_at(i)->_crc; } void populate_header(size_t alignment); - bool validate_header(); + bool validate_header(bool is_static); void invalidate(); + int crc() { return _header->_crc; } int version() { return _header->_version; } size_t alignment() { return _header->_alignment; } CompressedOops::Mode narrow_oop_mode() { return _header->_narrow_oop_mode; } @@ -234,6 +270,9 @@ char* read_only_tables_start() { return _header->_read_only_tables_start; } void set_read_only_tables_start(char* p) { _header->_read_only_tables_start = p; } + bool is_file_position_aligned() const; + void align_file_position(); + address cds_i2i_entry_code_buffers() { return _header->_cds_i2i_entry_code_buffers; } @@ -254,12 +293,21 @@ NOT_CDS(return NULL;) } + static void set_current_info(FileMapInfo* info) { + CDS_ONLY(_current_info = info;) + } + + static FileMapInfo* dynamic_info() { + CDS_ONLY(return _dynamic_archive_info;) + NOT_CDS(return NULL;) + } + static void assert_mark(bool check); // File manipulation. - bool initialize() NOT_CDS_RETURN_(false); - bool open_for_read(); - void open_for_write(); + bool initialize(bool is_static) NOT_CDS_RETURN_(false); + bool open_for_read(const char* path = NULL); + void open_for_write(const char* path = NULL); void write_header(); void write_region(int region, char* base, size_t size, bool read_only, bool allow_exec); @@ -269,6 +317,8 @@ bool print_log); void write_bytes(const void* buffer, size_t count); void write_bytes_aligned(const void* buffer, size_t count); + size_t read_bytes(void* buffer, size_t count); + char* map_regions(int regions[], char* saved_base[], size_t len); char* map_region(int i, char** top_ret); void map_heap_regions_impl() NOT_CDS_JAVA_HEAP_RETURN; void map_heap_regions() NOT_CDS_JAVA_HEAP_RETURN; @@ -278,6 +328,7 @@ int first_region_idx) NOT_CDS_JAVA_HEAP_RETURN; bool has_heap_regions() NOT_CDS_JAVA_HEAP_RETURN_(false); MemRegion get_heap_regions_range_with_current_oop_encoding_mode() NOT_CDS_JAVA_HEAP_RETURN_(MemRegion()); + void unmap_regions(int regions[], char* saved_base[], size_t len); void unmap_region(int i); bool verify_region_checksum(int i); void close(); @@ -291,7 +342,10 @@ // Errors. static void fail_stop(const char *msg, ...) ATTRIBUTE_PRINTF(1, 2); static void fail_continue(const char *msg, ...) ATTRIBUTE_PRINTF(1, 2); - + static bool memory_mapping_failed() { + CDS_ONLY(return _memory_mapping_failed;) + NOT_CDS(return false;) + } bool is_in_shared_region(const void* p, int idx) NOT_CDS_RETURN_(false); // Stop CDS sharing and unmap CDS regions. @@ -307,13 +361,7 @@ #endif static SharedClassPathEntry* shared_path(int index) { - if (index < 0) { - return NULL; - } - assert(index < _shared_path_table_size, "sanity"); - char* p = (char*)_shared_path_table->data(); - p += _shared_path_entry_size * index; - return (SharedClassPathEntry*)p; + return _shared_path_table.path_at(index); } static const char* shared_path_name(int index) { @@ -322,7 +370,7 @@ } static int get_number_of_shared_paths() { - return _shared_path_table_size; + return _shared_path_table.size(); } char* region_addr(int idx); @@ -330,7 +378,7 @@ private: bool map_heap_data(MemRegion **heap_mem, int first, int max, int* num, bool is_open = false) NOT_CDS_JAVA_HEAP_RETURN_(false); - bool verify_mapped_heap_regions(int first, int num) NOT_CDS_JAVA_HEAP_RETURN_(false); + bool region_crc_check(char* buf, size_t size, int expected_crc) NOT_CDS_RETURN_(false); void dealloc_archive_heap_regions(MemRegion* regions, int num, bool is_open) NOT_CDS_JAVA_HEAP_RETURN; CDSFileMapRegion* space_at(int i) { diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/heapShared.cpp --- a/src/hotspot/share/memory/heapShared.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/heapShared.cpp Fri May 17 08:29:55 2019 -0700 @@ -418,8 +418,7 @@ _run_time_subgraph_info_table.reset(); - int num_buckets = CompactHashtableWriter::default_num_buckets(d_table->_count); - CompactHashtableWriter writer(num_buckets, &stats); + CompactHashtableWriter writer(d_table->_count, &stats); CopyKlassSubGraphInfoToArchive copy(&writer); d_table->iterate(©); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/heapShared.hpp --- a/src/hotspot/share/memory/heapShared.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/heapShared.hpp Fri May 17 08:29:55 2019 -0700 @@ -286,16 +286,6 @@ idx <= MetaspaceShared::last_open_archive_heap_region)); NOT_CDS_JAVA_HEAP_RETURN_(false); } - static bool is_closed_archive_heap_region(int idx) { - CDS_JAVA_HEAP_ONLY(return (idx >= MetaspaceShared::first_closed_archive_heap_region && - idx <= MetaspaceShared::last_closed_archive_heap_region)); - NOT_CDS_JAVA_HEAP_RETURN_(false); - } - static bool is_open_archive_heap_region(int idx) { - CDS_JAVA_HEAP_ONLY(return (idx >= MetaspaceShared::first_open_archive_heap_region && - idx <= MetaspaceShared::last_open_archive_heap_region)); - NOT_CDS_JAVA_HEAP_RETURN_(false); - } static void set_closed_archive_heap_region_mapped() { CDS_JAVA_HEAP_ONLY(_closed_archive_heap_region_mapped = true); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/memRegion.cpp --- a/src/hotspot/share/memory/memRegion.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/memRegion.cpp Fri May 17 08:29:55 2019 -0700 @@ -118,4 +118,3 @@ void MemRegion::operator delete [](void* p) { FreeHeap(p); } - diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/metaspace.cpp --- a/src/hotspot/share/memory/metaspace.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/metaspace.cpp Fri May 17 08:29:55 2019 -0700 @@ -1220,6 +1220,10 @@ MetaspaceShared::initialize_runtime_shared_and_meta_spaces(); } + if (DynamicDumpSharedSpaces && !UseSharedSpaces) { + vm_exit_during_initialization("DynamicDumpSharedSpaces not supported when base CDS archive is not loaded", NULL); + } + if (!DumpSharedSpaces && !UseSharedSpaces) #endif // INCLUDE_CDS { diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/metaspaceClosure.cpp --- a/src/hotspot/share/memory/metaspaceClosure.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/metaspaceClosure.cpp Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019, 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 @@ -34,9 +34,20 @@ *(address*)mpp() = (address)p; } -void MetaspaceClosure::push_impl(MetaspaceClosure::Ref* ref, Writability w) { - if (ref->not_null()) { +void MetaspaceClosure::push_impl(MetaspaceClosure::Ref* ref) { + if (_nest_level < MAX_NEST_LEVEL) { + do_push(ref); + delete ref; + } else { + ref->set_next(_pending_refs); + _pending_refs = ref; + } +} + +void MetaspaceClosure::do_push(MetaspaceClosure::Ref* ref) { + if (ref->not_null()) { // FIXME: make this configurable, so DynamicArchiveBuilder mark all pointers bool read_only; + Writability w = ref->writability(); switch (w) { case _writable: read_only = false; @@ -48,12 +59,29 @@ assert(w == _default, "must be"); read_only = ref->is_read_only_by_default(); } + _nest_level ++; if (do_ref(ref, read_only)) { // true means we want to iterate the embedded pointer in ref->metaspace_pointers_do(this); } + _nest_level --; } } +void MetaspaceClosure::finish() { + assert(_nest_level == 0, "must be"); + while (_pending_refs != NULL) { + Ref* ref = _pending_refs; + _pending_refs = _pending_refs->next(); + do_push(ref); + delete ref; + } +} + +MetaspaceClosure::~MetaspaceClosure() { + assert(_pending_refs == NULL, + "you must explicitly call MetaspaceClosure::finish() to process all refs!"); +} + bool UniqueMetaspaceClosure::do_ref(MetaspaceClosure::Ref* ref, bool read_only) { bool* found = _has_been_visited.lookup(ref->obj()); if (found != NULL) { @@ -64,7 +92,6 @@ if (_has_been_visited.maybe_grow(MAX_TABLE_SIZE)) { log_info(cds, hashtables)("Expanded _has_been_visited table to %d", _has_been_visited.table_size()); } - do_unique_ref(ref, read_only); - return true; // Saw this for the first time: iterate the embedded pointers. + return do_unique_ref(ref, read_only); } } diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/metaspaceClosure.hpp --- a/src/hotspot/share/memory/metaspaceClosure.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/metaspaceClosure.hpp Fri May 17 08:29:55 2019 -0700 @@ -101,9 +101,15 @@ // Symbol* bar() { return (Symbol*) _obj; } // // [2] All Array dimensions are statically declared. - class Ref { + class Ref : public CHeapObj { + Writability _writability; + Ref* _next; + // Noncopyable. + Ref(const Ref&); + Ref& operator=(const Ref&); protected: virtual void** mpp() const = 0; + Ref(Writability w) : _writability(w), _next(NULL) {} public: virtual bool not_null() const = 0; virtual int size() const = 0; @@ -111,6 +117,7 @@ virtual void metaspace_pointers_do_at(MetaspaceClosure *it, address new_loc) const = 0; virtual MetaspaceObj::Type msotype() const = 0; virtual bool is_read_only_by_default() const = 0; + virtual ~Ref() {} address obj() const { // In some rare cases (see CPSlot in constantPool.hpp) we store some flags in the lowest @@ -119,8 +126,16 @@ return (address)(p & (~FLAG_MASK)); } + address* addr() const { + return (address*)mpp(); + } + void update(address new_loc) const; + Writability writability() const { return _writability; }; + void set_next(Ref* n) { _next = n; } + Ref* next() const { return _next; } + private: static const uintx FLAG_MASK = 0x03; @@ -143,7 +158,7 @@ } public: - ObjectRef(T** mpp) : _mpp(mpp) {} + ObjectRef(T** mpp, Writability w) : Ref(w), _mpp(mpp) {} virtual bool is_read_only_by_default() const { return T::is_read_only_by_default(); } virtual bool not_null() const { return dereference() != NULL; } @@ -170,7 +185,7 @@ } public: - PrimitiveArrayRef(Array** mpp) : _mpp(mpp) {} + PrimitiveArrayRef(Array** mpp, Writability w) : Ref(w), _mpp(mpp) {} // all Arrays are read-only by default virtual bool is_read_only_by_default() const { return true; } @@ -200,7 +215,7 @@ } public: - PointerArrayRef(Array** mpp) : _mpp(mpp) {} + PointerArrayRef(Array** mpp, Writability w) : Ref(w), _mpp(mpp) {} // all Arrays are read-only by default virtual bool is_read_only_by_default() const { return true; } @@ -224,9 +239,21 @@ } }; - void push_impl(Ref* ref, Writability w); + // If recursion is too deep, save the Refs in _pending_refs, and push them later using + // MetaspaceClosure::finish() + static const int MAX_NEST_LEVEL = 5; + Ref* _pending_refs; + int _nest_level; + + void push_impl(Ref* ref); + void do_push(Ref* ref); public: + MetaspaceClosure(): _pending_refs(NULL), _nest_level(0) {} + ~MetaspaceClosure(); + + void finish(); + // returns true if we want to keep iterating the pointers embedded inside virtual bool do_ref(Ref* ref, bool read_only) = 0; @@ -237,22 +264,19 @@ // C++ will try to match the "most specific" template function. This one will // will be matched if possible (if mpp is an Array<> of any pointer type). template void push(Array** mpp, Writability w = _default) { - PointerArrayRef ref(mpp); - push_impl(&ref, w); + push_impl(new PointerArrayRef(mpp, w)); } // If the above function doesn't match (mpp is an Array<>, but T is not a pointer type), then // this is the second choice. template void push(Array** mpp, Writability w = _default) { - PrimitiveArrayRef ref(mpp); - push_impl(&ref, w); + push_impl(new PrimitiveArrayRef(mpp, w)); } // If the above function doesn't match (mpp is not an Array<> type), then // this will be matched by default. template void push(T** mpp, Writability w = _default) { - ObjectRef ref(mpp); - push_impl(&ref, w); + push_impl(new ObjectRef(mpp, w)); } }; @@ -266,7 +290,7 @@ public: // Gets called the first time we discover an object. - virtual void do_unique_ref(Ref* ref, bool read_only) = 0; + virtual bool do_unique_ref(Ref* ref, bool read_only) = 0; UniqueMetaspaceClosure() : _has_been_visited(INITIAL_TABLE_SIZE) {} private: diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/metaspaceShared.cpp --- a/src/hotspot/share/memory/metaspaceShared.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/metaspaceShared.cpp Fri May 17 08:29:55 2019 -0700 @@ -48,10 +48,12 @@ #include "memory/metaspaceShared.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "memory/dynamicArchive.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/instanceClassLoaderKlass.hpp" #include "oops/instanceMirrorKlass.hpp" #include "oops/instanceRefKlass.hpp" +#include "oops/methodData.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" @@ -81,6 +83,7 @@ address MetaspaceShared::_cds_i2i_entry_code_buffers = NULL; size_t MetaspaceShared::_cds_i2i_entry_code_buffers_size = 0; size_t MetaspaceShared::_core_spaces_size = 0; +void* MetaspaceShared::_shared_metaspace_static_top = NULL; // The CDS archive is divided into the following regions: // mc - misc code (the method entry trampolines) @@ -112,105 +115,97 @@ // The s0/s1 and oa0/oa1 regions are populated inside HeapShared::archive_java_heap_objects. // Their layout is independent of the other 5 regions. -class DumpRegion { -private: - const char* _name; - char* _base; - char* _top; - char* _end; - bool _is_packed; - - char* expand_top_to(char* newtop) { - assert(is_allocatable(), "must be initialized and not packed"); - assert(newtop >= _top, "must not grow backwards"); - if (newtop > _end) { - MetaspaceShared::report_out_of_space(_name, newtop - _top); - ShouldNotReachHere(); - } - uintx delta = MetaspaceShared::object_delta_uintx(newtop); - if (delta > MAX_SHARED_DELTA) { - // This is just a sanity check and should not appear in any real world usage. This - // happens only if you allocate more than 2GB of shared objects and would require - // millions of shared classes. - vm_exit_during_initialization("Out of memory in the CDS archive", - "Please reduce the number of shared classes."); - } - - MetaspaceShared::commit_shared_space_to(newtop); - _top = newtop; - return _top; +char* DumpRegion::expand_top_to(char* newtop) { + assert(is_allocatable(), "must be initialized and not packed"); + assert(newtop >= _top, "must not grow backwards"); + if (newtop > _end) { + MetaspaceShared::report_out_of_space(_name, newtop - _top); + ShouldNotReachHere(); } - -public: - DumpRegion(const char* name) : _name(name), _base(NULL), _top(NULL), _end(NULL), _is_packed(false) {} - - char* allocate(size_t num_bytes, size_t alignment=BytesPerWord) { - char* p = (char*)align_up(_top, alignment); - char* newtop = p + align_up(num_bytes, alignment); - expand_top_to(newtop); - memset(p, 0, newtop - p); - return p; + uintx delta; + if (DynamicDumpSharedSpaces) { + delta = DynamicArchive::object_delta_uintx(newtop); + } else { + delta = MetaspaceShared::object_delta_uintx(newtop); } - - void append_intptr_t(intptr_t n) { - assert(is_aligned(_top, sizeof(intptr_t)), "bad alignment"); - intptr_t *p = (intptr_t*)_top; - char* newtop = _top + sizeof(intptr_t); - expand_top_to(newtop); - *p = n; + if (delta > MAX_SHARED_DELTA) { + // This is just a sanity check and should not appear in any real world usage. This + // happens only if you allocate more than 2GB of shared objects and would require + // millions of shared classes. + vm_exit_during_initialization("Out of memory in the CDS archive", + "Please reduce the number of shared classes."); } - char* base() const { return _base; } - char* top() const { return _top; } - char* end() const { return _end; } - size_t reserved() const { return _end - _base; } - size_t used() const { return _top - _base; } - bool is_packed() const { return _is_packed; } - bool is_allocatable() const { - return !is_packed() && _base != NULL; - } + MetaspaceShared::commit_shared_space_to(newtop); + _top = newtop; + return _top; +} + +char* DumpRegion::allocate(size_t num_bytes, size_t alignment) { + char* p = (char*)align_up(_top, alignment); + char* newtop = p + align_up(num_bytes, alignment); + expand_top_to(newtop); + memset(p, 0, newtop - p); + return p; +} - void print(size_t total_bytes) const { - tty->print_cr("%-3s space: " SIZE_FORMAT_W(9) " [ %4.1f%% of total] out of " SIZE_FORMAT_W(9) " bytes [%5.1f%% used] at " INTPTR_FORMAT, - _name, used(), percent_of(used(), total_bytes), reserved(), percent_of(used(), reserved()), p2i(_base)); +void DumpRegion::print(size_t total_bytes) const { + tty->print_cr("%-3s space: " SIZE_FORMAT_W(9) " [ %4.1f%% of total] out of " SIZE_FORMAT_W(9) " bytes [%5.1f%% used] at " INTPTR_FORMAT, + _name, used(), percent_of(used(), total_bytes), reserved(), percent_of(used(), reserved()), p2i(_base)); +} + +void DumpRegion::print_out_of_space_msg(const char* failing_region, size_t needed_bytes) { + tty->print("[%-8s] " PTR_FORMAT " - " PTR_FORMAT " capacity =%9d, allocated =%9d", + _name, p2i(_base), p2i(_top), int(_end - _base), int(_top - _base)); + if (strcmp(_name, failing_region) == 0) { + tty->print_cr(" required = %d", int(needed_bytes)); + } else { + tty->cr(); } - void print_out_of_space_msg(const char* failing_region, size_t needed_bytes) { - tty->print("[%-8s] " PTR_FORMAT " - " PTR_FORMAT " capacity =%9d, allocated =%9d", - _name, p2i(_base), p2i(_top), int(_end - _base), int(_top - _base)); - if (strcmp(_name, failing_region) == 0) { - tty->print_cr(" required = %d", int(needed_bytes)); - } else { - tty->cr(); - } - } +} - void init(const ReservedSpace* rs) { - _base = _top = rs->base(); - _end = rs->end(); +void DumpRegion::pack(DumpRegion* next) { + assert(!is_packed(), "sanity"); + _end = (char*)align_up(_top, Metaspace::reserve_alignment()); + _is_packed = true; + if (next != NULL) { + next->_base = next->_top = this->_end; + next->_end = MetaspaceShared::shared_rs()->end(); } - void init(char* b, char* t, char* e) { - _base = b; - _top = t; - _end = e; - } +} + +DumpRegion _mc_region("mc"), _ro_region("ro"), _rw_region("rw"), _md_region("md"), _od_region("od"); +size_t _total_closed_archive_region_size = 0, _total_open_archive_region_size = 0; - void pack(DumpRegion* next = NULL) { - assert(!is_packed(), "sanity"); - _end = (char*)align_up(_top, Metaspace::reserve_alignment()); - _is_packed = true; - if (next != NULL) { - next->_base = next->_top = this->_end; - next->_end = MetaspaceShared::shared_rs()->end(); - } +void MetaspaceShared::init_shared_dump_space(DumpRegion* first_space, address first_space_bottom) { + // Start with 0 committed bytes. The memory will be committed as needed by + // MetaspaceShared::commit_shared_space_to(). + if (!_shared_vs.initialize(_shared_rs, 0)) { + vm_exit_during_initialization("Unable to allocate memory for shared space"); } - bool contains(char* p) { - return base() <= p && p < top(); - } -}; + first_space->init(&_shared_rs, (char*)first_space_bottom); +} + +DumpRegion* MetaspaceShared::misc_code_dump_space() { + return &_mc_region; +} + +DumpRegion* MetaspaceShared::read_write_dump_space() { + return &_rw_region; +} +DumpRegion* MetaspaceShared::read_only_dump_space() { + return &_ro_region; +} -DumpRegion _mc_region("mc"), _ro_region("ro"), _rw_region("rw"), _md_region("md"); -size_t _total_closed_archive_region_size = 0, _total_open_archive_region_size = 0; +DumpRegion* MetaspaceShared::optional_data_dump_space() { + return &_od_region; +} + +void MetaspaceShared::pack_dump_space(DumpRegion* current, DumpRegion* next, + ReservedSpace* rs) { + current->pack(next); +} char* MetaspaceShared::misc_code_space_alloc(size_t num_bytes) { return _mc_region.allocate(num_bytes); @@ -226,20 +221,23 @@ // If using shared space, open the file that contains the shared space // and map in the memory before initializing the rest of metaspace (so // the addresses don't conflict) - address cds_address = NULL; - FileMapInfo* mapinfo = new FileMapInfo(); + FileMapInfo* mapinfo = new FileMapInfo(true); // Open the shared archive file, read and validate the header. If // initialization fails, shared spaces [UseSharedSpaces] are // disabled and the file is closed. // Map in spaces now also - if (mapinfo->initialize() && map_shared_spaces(mapinfo)) { + if (mapinfo->initialize(true) && map_shared_spaces(mapinfo)) { size_t cds_total = core_spaces_size(); - cds_address = (address)mapinfo->region_addr(0); + address cds_address = (address)mapinfo->region_addr(0); + char* cds_end = (char *)align_up(cds_address + cds_total, + Metaspace::reserve_alignment()); + + // Mapping the dynamic archive before allocating the class space + cds_end = initialize_dynamic_runtime_shared_spaces((char*)cds_address, cds_end); + #ifdef _LP64 if (Metaspace::using_class_space()) { - char* cds_end = (char*)(cds_address + cds_total); - cds_end = (char *)align_up(cds_end, Metaspace::reserve_alignment()); // If UseCompressedClassPointers is set then allocate the metaspace area // above the heap and above the CDS area (if it exists). Metaspace::allocate_metaspace_compressed_klass_ptrs(cds_end, cds_address); @@ -255,6 +253,31 @@ } } +char* MetaspaceShared::initialize_dynamic_runtime_shared_spaces( + char* static_start, char* static_end) { + assert(UseSharedSpaces, "must be runtime"); + char* cds_end = static_end; + if (!DynamicDumpSharedSpaces) { + address dynamic_top = DynamicArchive::map(); + if (dynamic_top != NULL) { + assert(dynamic_top > (address)static_start, "Unexpected layout"); + MetaspaceObj::expand_shared_metaspace_range(dynamic_top); + cds_end = (char *)align_up(dynamic_top, Metaspace::reserve_alignment()); + } + } + return cds_end; +} + +ReservedSpace* MetaspaceShared::reserve_shared_rs(size_t size, size_t alignment, + bool large, char* requested_address) { + if (requested_address != NULL) { + _shared_rs = ReservedSpace(size, alignment, large, requested_address); + } else { + _shared_rs = ReservedSpace(size, alignment, large); + } + return &_shared_rs; +} + void MetaspaceShared::initialize_dumptime_shared_and_meta_spaces() { assert(DumpSharedSpaces, "should be called for dump time only"); const size_t reserve_alignment = Metaspace::reserve_alignment(); @@ -280,12 +303,14 @@ #endif // First try to reserve the space at the specified SharedBaseAddress. - _shared_rs = ReservedSpace(cds_total, reserve_alignment, large_pages, shared_base); + //_shared_rs = ReservedSpace(cds_total, reserve_alignment, large_pages, shared_base); + reserve_shared_rs(cds_total, reserve_alignment, large_pages, shared_base); if (_shared_rs.is_reserved()) { assert(shared_base == 0 || _shared_rs.base() == shared_base, "should match"); } else { // Get a mmap region anywhere if the SharedBaseAddress fails. - _shared_rs = ReservedSpace(cds_total, reserve_alignment, large_pages); + //_shared_rs = ReservedSpace(cds_total, reserve_alignment, large_pages); + reserve_shared_rs(cds_total, reserve_alignment, large_pages, NULL); } if (!_shared_rs.is_reserved()) { vm_exit_during_initialization("Unable to reserve memory for shared space", @@ -324,13 +349,7 @@ CompressedClassSpaceSize, p2i(tmp_class_space.base())); #endif - // Start with 0 committed bytes. The memory will be committed as needed by - // MetaspaceShared::commit_shared_space_to(). - if (!_shared_vs.initialize(_shared_rs, 0)) { - vm_exit_during_initialization("Unable to allocate memory for shared space"); - } - - _mc_region.init(&_shared_rs); + init_shared_dump_space(&_mc_region); SharedBaseAddress = (size_t)_shared_rs.base(); tty->print_cr("Allocated shared space: " SIZE_FORMAT " bytes at " PTR_FORMAT, _shared_rs.size(), p2i(_shared_rs.base())); @@ -342,9 +361,16 @@ int size = FileMapInfo::get_number_of_shared_paths(); if (size > 0) { SystemDictionaryShared::allocate_shared_data_arrays(size, THREAD); - FileMapHeader* header = FileMapInfo::current_info()->header(); - ClassLoaderExt::init_paths_start_index(header->_app_class_paths_start_index); - ClassLoaderExt::init_app_module_paths_start_index(header->_app_module_paths_start_index); + if (!DynamicDumpSharedSpaces) { + FileMapHeader* header; + if (FileMapInfo::dynamic_info() == NULL) { + header = FileMapInfo::current_info()->header(); + } else { + header = FileMapInfo::dynamic_info()->header(); + } + ClassLoaderExt::init_paths_start_index(header->_app_class_paths_start_index); + ClassLoaderExt::init_app_module_paths_start_index(header->_app_module_paths_start_index); + } } } } @@ -405,7 +431,7 @@ } void MetaspaceShared::commit_shared_space_to(char* newtop) { - assert(DumpSharedSpaces, "dump-time only"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "dump-time only"); char* base = _shared_rs.base(); size_t need_committed_size = newtop - base; size_t has_committed_size = _shared_vs.committed_size(); @@ -417,7 +443,8 @@ size_t preferred_bytes = 1 * M; size_t uncommitted = _shared_vs.reserved_size() - has_committed_size; - size_t commit = MAX2(min_bytes, preferred_bytes); + size_t commit =MAX2(min_bytes, preferred_bytes); + commit = MIN2(commit, uncommitted); assert(commit <= uncommitted, "sanity"); bool result = _shared_vs.expand_by(commit, false); @@ -465,6 +492,9 @@ InstanceMirrorKlass::serialize_offsets(soc); soc->do_tag(--tag); + serialize_cloned_cpp_vtptrs(soc); + soc->do_tag(--tag); + soc->do_tag(666); } @@ -484,6 +514,19 @@ return _cds_i2i_entry_code_buffers; } +uintx MetaspaceShared::object_delta_uintx(void* obj) { + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, + "supported only for dumping"); + if (DumpSharedSpaces) { + assert(shared_rs()->contains(obj), "must be"); + } else { + assert(is_in_shared_metaspace(obj) || DynamicArchive::is_in_target_space(obj), "must be"); + } + address base_address = address(SharedBaseAddress); + uintx deltax = address(obj) - base_address; + return deltax; +} + // Global object for holding classes that have been loaded. Since this // is run at a safepoint just before exit, this is the entire set of classes. static GrowableArray* _global_klass_objects; @@ -589,17 +632,21 @@ Klass* k = _global_klass_objects->at(i); if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - for (int i = 0; i < ik->methods()->length(); i++) { - Method* m = ik->methods()->at(i); - rewrite_nofast_bytecode(m); - Fingerprinter fp(m); - // The side effect of this call sets method's fingerprint field. - fp.fingerprint(); - } + MetaspaceShared::rewrite_nofast_bytecodes_and_calculate_fingerprints(ik); } } } +void MetaspaceShared::rewrite_nofast_bytecodes_and_calculate_fingerprints(InstanceKlass* ik) { + for (int i = 0; i < ik->methods()->length(); i++) { + Method* m = ik->methods()->at(i); + rewrite_nofast_bytecode(m); + Fingerprinter fp(m); + // The side effect of this call sets method's fingerprint field. + fp.fingerprint(); + } +} + // Objects of the Metadata types (such as Klass and ConstantPool) have C++ vtables. // (In GCC this is the field ::_vptr, i.e., first word in the object.) // @@ -686,7 +733,7 @@ intptr_t* p = clone_vtable(name, _info); assert((char*)p == _md_region.top(), "must be"); - return p; + return _info->cloned_vtable(); } template @@ -759,7 +806,7 @@ } #define ALLOC_CPP_VTABLE_CLONE(c) \ - CppVtableCloner::allocate(#c); + _cloned_cpp_vtptrs[c##_Kind] = CppVtableCloner::allocate(#c); #define CLONE_CPP_VTABLE(c) \ p = CppVtableCloner::clone_vtable(#c, (CppVtableInfo*)p); @@ -767,6 +814,85 @@ #define ZERO_CPP_VTABLE(c) \ CppVtableCloner::zero_vtable_clone(); +//------------------------------ for DynamicDumpSharedSpaces - start +#define DECLARE_CLONED_VTABLE_KIND(c) c ## _Kind, + +enum { + CPP_VTABLE_PATCH_TYPES_DO(DECLARE_CLONED_VTABLE_KIND) + _num_cloned_vtable_kinds +}; + +static intptr_t** _cloned_cpp_vtptrs = NULL; + +void MetaspaceShared::serialize_cloned_cpp_vtptrs(SerializeClosure* soc) { + soc->do_ptr((void**)&_cloned_cpp_vtptrs); +} + +intptr_t* MetaspaceShared::fix_cpp_vtable_for_dynamic_archive(MetaspaceObj::Type msotype, address obj) { + assert(DynamicDumpSharedSpaces, "must"); + int kind = -1; + switch (msotype) { + case MetaspaceObj::SymbolType: + case MetaspaceObj::TypeArrayU1Type: + case MetaspaceObj::TypeArrayU2Type: + case MetaspaceObj::TypeArrayU4Type: + case MetaspaceObj::TypeArrayU8Type: + case MetaspaceObj::TypeArrayOtherType: + case MetaspaceObj::ConstMethodType: + case MetaspaceObj::ConstantPoolCacheType: + case MetaspaceObj::AnnotationsType: + case MetaspaceObj::MethodCountersType: + // These have no vtables. + break; + case MetaspaceObj::ClassType: + { + Klass* k = (Klass*)obj; + assert(k->is_klass(), "must be"); + if (k->is_instance_klass()) { + kind = InstanceKlass_Kind; + } else { + assert(k->is_objArray_klass(), + "We shouldn't archive any other klasses in DynamicDumpSharedSpaces"); + kind = ObjArrayKlass_Kind; + } + } + break; + + case MetaspaceObj::MethodType: + { + Method* m = (Method*)obj; + assert(m->is_method(), "must be"); + kind = Method_Kind; + } + break; + + case MetaspaceObj::MethodDataType: + // We don't archive MethodData <-- should have been removed in removed_unsharable_info + ShouldNotReachHere(); + break; + + case MetaspaceObj::ConstantPoolType: + { + ConstantPool *cp = (ConstantPool*)obj; + assert(cp->is_constantPool(), "must be"); + kind = ConstantPool_Kind; + } + break; + + default: + ShouldNotReachHere(); + } + + if (kind >= 0) { + assert(kind < _num_cloned_vtable_kinds, "must be"); + return _cloned_cpp_vtptrs[kind]; + } else { + return NULL; + } +} + +//------------------------------ for DynamicDumpSharedSpaces - end + // This can be called at both dump time and run time. intptr_t* MetaspaceShared::clone_cpp_vtables(intptr_t* p) { assert(DumpSharedSpaces || UseSharedSpaces, "sanity"); @@ -830,55 +956,27 @@ return CppVtableCloner::is_valid_shared_object(m); } -// Closure for serializing initialization data out to a data area to be -// written to the shared file. - -class WriteClosure : public SerializeClosure { -private: - DumpRegion* _dump_region; - -public: - WriteClosure(DumpRegion* r) { - _dump_region = r; +void WriteClosure::do_oop(oop* o) { + if (*o == NULL) { + _dump_region->append_intptr_t(0); + } else { + assert(HeapShared::is_heap_object_archiving_allowed(), + "Archiving heap object is not allowed"); + _dump_region->append_intptr_t( + (intptr_t)CompressedOops::encode_not_null(*o)); } - - void do_ptr(void** p) { - _dump_region->append_intptr_t((intptr_t)*p); - } - - void do_u4(u4* p) { - void* ptr = (void*)(uintx(*p)); - do_ptr(&ptr); - } - - void do_tag(int tag) { - _dump_region->append_intptr_t((intptr_t)tag); - } +} - void do_oop(oop* o) { - if (*o == NULL) { - _dump_region->append_intptr_t(0); - } else { - assert(HeapShared::is_heap_object_archiving_allowed(), - "Archiving heap object is not allowed"); - _dump_region->append_intptr_t( - (intptr_t)CompressedOops::encode_not_null(*o)); - } +void WriteClosure::do_region(u_char* start, size_t size) { + assert((intptr_t)start % sizeof(intptr_t) == 0, "bad alignment"); + assert(size % sizeof(intptr_t) == 0, "bad size"); + do_tag((int)size); + while (size > 0) { + _dump_region->append_intptr_t(*(intptr_t*)start); + start += sizeof(intptr_t); + size -= sizeof(intptr_t); } - - void do_region(u_char* start, size_t size) { - assert((intptr_t)start % sizeof(intptr_t) == 0, "bad alignment"); - assert(size % sizeof(intptr_t) == 0, "bad size"); - do_tag((int)size); - while (size > 0) { - _dump_region->append_intptr_t(*(intptr_t*)start); - start += sizeof(intptr_t); - size -= sizeof(intptr_t); - } - } - - bool reading() const { return false; } -}; +} // This is for dumping detailed statistics for the allocations // in the shared spaces. @@ -1166,20 +1264,22 @@ public: ShallowCopier(bool read_only) : _read_only(read_only) {} - virtual void do_unique_ref(Ref* ref, bool read_only) { + virtual bool do_unique_ref(Ref* ref, bool read_only) { if (read_only == _read_only) { allocate(ref, read_only); } + return true; // recurse into ref.obj() } }; // Relocate embedded pointers within a MetaspaceObj's shallow copy class ShallowCopyEmbeddedRefRelocator: public UniqueMetaspaceClosure { public: - virtual void do_unique_ref(Ref* ref, bool read_only) { + virtual bool do_unique_ref(Ref* ref, bool read_only) { address new_loc = get_new_loc(ref); RefRelocator refer; ref->metaspace_pointers_do_at(&refer, new_loc); + return true; // recurse into ref.obj() } }; @@ -1294,6 +1394,8 @@ Universe::metaspace_pointers_do(it); SymbolTable::metaspace_pointers_do(it); vmSymbols::metaspace_pointers_do(it); + + it->finish(); } static Klass* get_relocated_klass(Klass* orig_klass) { @@ -1336,6 +1438,9 @@ char* start = _ro_region.top(); + size_t vtptrs_bytes = _num_cloned_vtable_kinds * sizeof(intptr_t*); + _cloned_cpp_vtptrs = (intptr_t**)_ro_region.allocate(vtptrs_bytes, sizeof(intptr_t*)); + // Write the other data to the output array. WriteClosure wc(&_ro_region); MetaspaceShared::serialize(&wc); @@ -1354,6 +1459,7 @@ // in the VM thread. // (2) ArchiveCompactor needs to work with a stable set of MetaspaceObjs. Metaspace::freeze(); + DEBUG_ONLY(SystemDictionaryShared::NoClassLoadingMark nclm); Thread* THREAD = VMThread::vm_thread(); @@ -1441,7 +1547,7 @@ // Create and write the archive file that maps the shared spaces. - FileMapInfo* mapinfo = new FileMapInfo(); + FileMapInfo* mapinfo = new FileMapInfo(true); mapinfo->populate_header(os::vm_allocation_granularity()); mapinfo->set_read_only_tables_start(read_only_tables_start); mapinfo->set_misc_data_patching_start(vtbl_list); @@ -1816,67 +1922,55 @@ } #endif // INCLUDE_CDS_JAVA_HEAP -// Closure for serializing initialization data in from a data area -// (ptr_array) read from the shared file. - -class ReadClosure : public SerializeClosure { -private: - intptr_t** _ptr_array; - - inline intptr_t nextPtr() { - return *(*_ptr_array)++; - } +void ReadClosure::do_ptr(void** p) { + assert(*p == NULL, "initializing previous initialized pointer."); + intptr_t obj = nextPtr(); + assert((intptr_t)obj >= 0 || (intptr_t)obj < -100, + "hit tag while initializing ptrs."); + *p = (void*)obj; +} -public: - ReadClosure(intptr_t** ptr_array) { _ptr_array = ptr_array; } +void ReadClosure::do_u4(u4* p) { + intptr_t obj = nextPtr(); + *p = (u4)(uintx(obj)); +} - void do_ptr(void** p) { - assert(*p == NULL, "initializing previous initialized pointer."); - intptr_t obj = nextPtr(); - assert((intptr_t)obj >= 0 || (intptr_t)obj < -100, - "hit tag while initializing ptrs."); - *p = (void*)obj; - } - - void do_u4(u4* p) { - intptr_t obj = nextPtr(); - *p = (u4)(uintx(obj)); - } +void ReadClosure::do_tag(int tag) { + int old_tag; + old_tag = (int)(intptr_t)nextPtr(); + // do_int(&old_tag); + assert(tag == old_tag, "old tag doesn't match"); + FileMapInfo::assert_mark(tag == old_tag); +} - void do_tag(int tag) { - int old_tag; - old_tag = (int)(intptr_t)nextPtr(); - // do_int(&old_tag); - assert(tag == old_tag, "old tag doesn't match"); - FileMapInfo::assert_mark(tag == old_tag); +void ReadClosure::do_oop(oop *p) { + narrowOop o = (narrowOop)nextPtr(); + if (o == 0 || !HeapShared::open_archive_heap_region_mapped()) { + p = NULL; + } else { + assert(HeapShared::is_heap_object_archiving_allowed(), + "Archived heap object is not allowed"); + assert(HeapShared::open_archive_heap_region_mapped(), + "Open archive heap region is not mapped"); + *p = HeapShared::decode_from_archive(o); } +} - void do_oop(oop *p) { - narrowOop o = (narrowOop)nextPtr(); - if (o == 0 || !HeapShared::open_archive_heap_region_mapped()) { - p = NULL; - } else { - assert(HeapShared::is_heap_object_archiving_allowed(), - "Archived heap object is not allowed"); - assert(HeapShared::open_archive_heap_region_mapped(), - "Open archive heap region is not mapped"); - *p = HeapShared::decode_from_archive(o); - } +void ReadClosure::do_region(u_char* start, size_t size) { + assert((intptr_t)start % sizeof(intptr_t) == 0, "bad alignment"); + assert(size % sizeof(intptr_t) == 0, "bad size"); + do_tag((int)size); + while (size > 0) { + *(intptr_t*)start = nextPtr(); + start += sizeof(intptr_t); + size -= sizeof(intptr_t); } +} - void do_region(u_char* start, size_t size) { - assert((intptr_t)start % sizeof(intptr_t) == 0, "bad alignment"); - assert(size % sizeof(intptr_t) == 0, "bad size"); - do_tag((int)size); - while (size > 0) { - *(intptr_t*)start = nextPtr(); - start += sizeof(intptr_t); - size -= sizeof(intptr_t); - } - } - - bool reading() const { return true; } -}; +void MetaspaceShared::set_shared_metaspace_range(void* base, void* top) { + _shared_metaspace_static_top = top; + MetaspaceObj::set_shared_metaspace_range(base, top); +} // Return true if given address is in the misc data region bool MetaspaceShared::is_in_shared_region(const void* p, int idx) { @@ -1890,6 +1984,15 @@ return false; } +bool MetaspaceShared::is_shared_dynamic(void* p) { + if ((p < MetaspaceObj::shared_metaspace_top()) && + (p >= _shared_metaspace_static_top)) { + return true; + } else { + return false; + } +} + // Map shared spaces at requested addresses and return if succeeded. bool MetaspaceShared::map_shared_spaces(FileMapInfo* mapinfo) { size_t image_alignment = mapinfo->alignment(); @@ -1904,42 +2007,23 @@ assert(!DumpSharedSpaces, "Should not be called with DumpSharedSpaces"); - char* ro_base = NULL; char* ro_top; - char* rw_base = NULL; char* rw_top; - char* mc_base = NULL; char* mc_top; - char* md_base = NULL; char* md_top; + // Map each shared region + int regions[] = {mc, rw, ro, md}; + size_t len = sizeof(regions)/sizeof(int); + char* saved_base[] = {NULL, NULL, NULL, NULL}; + char* top = mapinfo->map_regions(regions, saved_base, len ); - // Map each shared region - if ((mc_base = mapinfo->map_region(mc, &mc_top)) != NULL && - (rw_base = mapinfo->map_region(rw, &rw_top)) != NULL && - (ro_base = mapinfo->map_region(ro, &ro_top)) != NULL && - (md_base = mapinfo->map_region(md, &md_top)) != NULL && + if (top != NULL && (image_alignment == (size_t)os::vm_allocation_granularity()) && mapinfo->validate_shared_path_table()) { // Success -- set up MetaspaceObj::_shared_metaspace_{base,top} for // fast checking in MetaspaceShared::is_in_shared_metaspace() and // MetaspaceObj::is_shared(). - // - // We require that mc->rw->ro->md to be laid out consecutively, with no - // gaps between them. That way, we can ensure that the OS won't be able to - // allocate any new memory spaces inside _shared_metaspace_{base,top}, which - // would mess up the simple comparision in MetaspaceShared::is_in_shared_metaspace(). - assert(mc_base < ro_base && mc_base < rw_base && mc_base < md_base, "must be"); - assert(md_top > ro_top && md_top > rw_top && md_top > mc_top , "must be"); - assert(mc_top == rw_base, "must be"); - assert(rw_top == ro_base, "must be"); - assert(ro_top == md_base, "must be"); - _core_spaces_size = mapinfo->core_spaces_size(); - MetaspaceObj::set_shared_metaspace_range((void*)mc_base, (void*)md_top); + set_shared_metaspace_range((void*)saved_base[0], (void*)top); return true; } else { - // If there was a failure in mapping any of the spaces, unmap the ones - // that succeeded - if (ro_base != NULL) mapinfo->unmap_region(ro); - if (rw_base != NULL) mapinfo->unmap_region(rw); - if (mc_base != NULL) mapinfo->unmap_region(mc); - if (md_base != NULL) mapinfo->unmap_region(md); + mapinfo->unmap_regions(regions, saved_base, len); #ifndef _WINDOWS // Release the entire mapped region shared_rs.release(); @@ -1970,6 +2054,9 @@ // The rest of the data is now stored in the RW region buffer = mapinfo->read_only_tables_start(); + // Skip over _cloned_cpp_vtptrs; + buffer += _num_cloned_vtable_kinds * sizeof(intptr_t*); + // Verify various attributes of the archive, plus initialize the // shared string/symbol tables intptr_t* array = (intptr_t*)buffer; @@ -2009,6 +2096,12 @@ if (!mapinfo->remap_shared_readonly_as_readwrite()) { return false; } + if (FileMapInfo::dynamic_info() != NULL) { + mapinfo = FileMapInfo::dynamic_info(); + if (!mapinfo->remap_shared_readonly_as_readwrite()) { + return false; + } + } _remapped_readwrite = true; } return true; diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/metaspaceShared.hpp --- a/src/hotspot/share/memory/metaspaceShared.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/metaspaceShared.hpp Fri May 17 08:29:55 2019 -0700 @@ -47,6 +47,124 @@ CompactHashtableStats string; }; +#if INCLUDE_CDS +class DumpRegion { +private: + const char* _name; + char* _base; + char* _top; + char* _end; + bool _is_packed; + +public: + DumpRegion(const char* name) : _name(name), _base(NULL), _top(NULL), _end(NULL), _is_packed(false) {} + + char* expand_top_to(char* newtop); + char* allocate(size_t num_bytes, size_t alignment=BytesPerWord); + + void append_intptr_t(intptr_t n) { + assert(is_aligned(_top, sizeof(intptr_t)), "bad alignment"); + intptr_t *p = (intptr_t*)_top; + char* newtop = _top + sizeof(intptr_t); + expand_top_to(newtop); + *p = n; + } + + char* base() const { return _base; } + char* top() const { return _top; } + char* end() const { return _end; } + size_t reserved() const { return _end - _base; } + size_t used() const { return _top - _base; } + bool is_packed() const { return _is_packed; } + bool is_allocatable() const { + return !is_packed() && _base != NULL; + } + + void print(size_t total_bytes) const; + void print_out_of_space_msg(const char* failing_region, size_t needed_bytes); + + void init(const ReservedSpace* rs, char* base) { + if (base == NULL) { + base = rs->base(); + } + assert(rs->contains(base), "must be"); + _base = _top = base; + _end = rs->end(); + } + void init(char* b, char* t, char* e) { + _base = b; + _top = t; + _end = e; + } + + void pack(DumpRegion* next = NULL); + + bool contains(char* p) { + return base() <= p && p < top(); + } +}; + +// Closure for serializing initialization data out to a data area to be +// written to the shared file. + +class WriteClosure : public SerializeClosure { +private: + DumpRegion* _dump_region; + +public: + WriteClosure(DumpRegion* r) { + _dump_region = r; + } + + void do_ptr(void** p) { + _dump_region->append_intptr_t((intptr_t)*p); + } + + void do_u4(u4* p) { + void* ptr = (void*)(uintx(*p)); + do_ptr(&ptr); + } + + void do_tag(int tag) { + _dump_region->append_intptr_t((intptr_t)tag); + } + + void do_oop(oop* o); + + void do_region(u_char* start, size_t size); + + bool reading() const { return false; } +}; + +// Closure for serializing initialization data in from a data area +// (ptr_array) read from the shared file. + +class ReadClosure : public SerializeClosure { +private: + intptr_t** _ptr_array; + + inline intptr_t nextPtr() { + return *(*_ptr_array)++; + } + +public: + ReadClosure(intptr_t** ptr_array) { _ptr_array = ptr_array; } + + void do_ptr(void** p); + + void do_u4(u4* p); + + void do_tag(int tag); + + void do_oop(oop *p); + + void do_region(u_char* start, size_t size); + + bool reading() const { return true; } +}; + +#endif + // Class Data Sharing Support class MetaspaceShared : AllStatic { @@ -61,6 +179,7 @@ static address _cds_i2i_entry_code_buffers; static size_t _cds_i2i_entry_code_buffers_size; static size_t _core_spaces_size; + static void* _shared_metaspace_static_top; public: enum { // core archive spaces @@ -102,16 +221,12 @@ } static void initialize_dumptime_shared_and_meta_spaces() NOT_CDS_RETURN; static void initialize_runtime_shared_and_meta_spaces() NOT_CDS_RETURN; + static char* initialize_dynamic_runtime_shared_spaces( + char* static_start, char* static_end) NOT_CDS_RETURN_(NULL); static void post_initialize(TRAPS) NOT_CDS_RETURN; - // Delta of this object from the bottom of the archive. - static uintx object_delta_uintx(void* obj) { - assert(DumpSharedSpaces, "supported only for dumping"); - assert(shared_rs()->contains(obj), "must be"); - address base_address = address(shared_rs()->base()); - uintx deltax = address(obj) - base_address; - return deltax; - } + // Delta of this object from SharedBaseAddress + static uintx object_delta_uintx(void* obj); static u4 object_delta_u4(void* obj) { // offset is guaranteed to be less than MAX_SHARED_DELTA in DumpRegion::expand_top_to() @@ -134,15 +249,25 @@ return (p < MetaspaceObj::shared_metaspace_top() && p >= MetaspaceObj::shared_metaspace_base()); } + static address shared_metaspace_top() { + return (address)MetaspaceObj::shared_metaspace_top(); + } + + static void set_shared_metaspace_range(void* base, void* top) NOT_CDS_RETURN; + // Return true if given address is in the shared region corresponding to the idx static bool is_in_shared_region(const void* p, int idx) NOT_CDS_RETURN_(false); static bool is_in_trampoline_frame(address addr) NOT_CDS_RETURN_(false); + static bool is_shared_dynamic(void* p) NOT_CDS_RETURN_(false); + static void allocate_cpp_vtable_clones(); static intptr_t* clone_cpp_vtables(intptr_t* p); static void zero_cpp_vtable_clones_for_writing(); static void patch_cpp_vtable_pointers(); + static void serialize_cloned_cpp_vtptrs(SerializeClosure* sc); + static bool is_valid_shared_method(const Method* m) NOT_CDS_RETURN_(false); static void serialize(SerializeClosure* sc) NOT_CDS_RETURN; @@ -165,6 +290,20 @@ static bool try_link_class(InstanceKlass* ik, TRAPS); static void link_and_cleanup_shared_classes(TRAPS); +#if INCLUDE_CDS + static ReservedSpace* reserve_shared_rs(size_t size, size_t alignment, + bool large, char* requested_address); + static void init_shared_dump_space(DumpRegion* first_space, address first_space_bottom = NULL); + static DumpRegion* misc_code_dump_space(); + static DumpRegion* read_write_dump_space(); + static DumpRegion* read_only_dump_space(); + static DumpRegion* optional_data_dump_space(); + static void pack_dump_space(DumpRegion* current, DumpRegion* next, + ReservedSpace* rs); + + static void rewrite_nofast_bytecodes_and_calculate_fingerprints(InstanceKlass* ik); +#endif + // Allocate a block of memory from the "mc", "ro", or "rw" regions. static char* misc_code_space_alloc(size_t num_bytes); static char* read_only_space_alloc(size_t num_bytes); @@ -181,6 +320,12 @@ #endif } + template + static size_t ro_array_bytesize(int length) { + size_t byte_size = Array::byte_sizeof(length, sizeof(T)); + return align_up(byte_size, BytesPerWord); + } + static address cds_i2i_entry_code_buffers(size_t total_size); static address cds_i2i_entry_code_buffers() { @@ -193,6 +338,8 @@ static Klass* get_relocated_klass(Klass *k); + static intptr_t* fix_cpp_vtable_for_dynamic_archive(MetaspaceObj::Type msotype, address obj); + private: static void read_extra_data(const char* filename, TRAPS) NOT_CDS_RETURN; }; diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/memory/universe.cpp --- a/src/hotspot/share/memory/universe.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/memory/universe.cpp Fri May 17 08:29:55 2019 -0700 @@ -711,13 +711,14 @@ { SymbolTable::create_table(); StringTable::create_table(); + } #if INCLUDE_CDS - if (DumpSharedSpaces) { - MetaspaceShared::prepare_for_dumping(); - } + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { + MetaspaceShared::prepare_for_dumping(); + } #endif - } + if (strlen(VerifySubSet) > 0) { Universe::initialize_verify_flags(); } diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/oops/constMethod.hpp --- a/src/hotspot/share/oops/constMethod.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/oops/constMethod.hpp Fri May 17 08:29:55 2019 -0700 @@ -288,12 +288,16 @@ // adapter void set_adapter_entry(AdapterHandlerEntry* adapter) { - assert(!is_shared(), "shared methods have fixed adapter_trampoline"); + assert(!is_shared(), + "shared methods in archive have fixed adapter_trampoline"); _adapter = adapter; } void set_adapter_trampoline(AdapterHandlerEntry** trampoline) { - assert(DumpSharedSpaces, "must be"); - assert(*trampoline == NULL, "must be NULL during dump time, to be initialized at run time"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "must be"); + if (DumpSharedSpaces) { + assert(*trampoline == NULL, + "must be NULL during dump time, to be initialized at run time"); + } _adapter_trampoline = trampoline; } void update_adapter_trampoline(AdapterHandlerEntry* adapter) { diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/oops/constantPool.cpp --- a/src/hotspot/share/oops/constantPool.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/oops/constantPool.cpp Fri May 17 08:29:55 2019 -0700 @@ -371,7 +371,9 @@ // If archiving heap objects is not allowed, clear the resolved references. // Otherwise, it is cleared after the resolved references array is cached // (see archive_resolved_references()). - if (!HeapShared::is_heap_object_archiving_allowed()) { + // If DynamicDumpSharedSpaces is enabled, clear the resolved references also + // as java objects are not archived in the top layer. + if (!HeapShared::is_heap_object_archiving_allowed() || DynamicDumpSharedSpaces) { set_resolved_references(NULL); } @@ -382,7 +384,16 @@ _flags |= (_on_stack | _is_shared); int num_klasses = 0; for (int index = 1; index < length(); index++) { // Index 0 is unused - assert(!tag_at(index).is_unresolved_klass_in_error(), "This must not happen during dump time"); + if (!DynamicDumpSharedSpaces) { + assert(!tag_at(index).is_unresolved_klass_in_error(), "This must not happen during static dump time"); + } else { + if (tag_at(index).is_unresolved_klass_in_error() || + tag_at(index).is_method_handle_in_error() || + tag_at(index).is_method_type_in_error() || + tag_at(index).is_dynamic_constant_in_error()) { + tag_at_put(index, JVM_CONSTANT_UnresolvedClass); + } + } if (tag_at(index).is_klass()) { // This class was resolved as a side effect of executing Java code // during dump time. We need to restore it back to an UnresolvedClass, diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/oops/cpCache.cpp --- a/src/hotspot/share/oops/cpCache.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/oops/cpCache.cpp Fri May 17 08:29:55 2019 -0700 @@ -697,7 +697,7 @@ } void ConstantPoolCache::walk_entries_for_initialization(bool check_only) { - assert(DumpSharedSpaces, "sanity"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "sanity"); // When dumping the archive, we want to clean up the ConstantPoolCache // to remove any effect of linking due to the execution of Java code -- // each ConstantPoolCacheEntry will have the same contents as if diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/oops/instanceKlass.cpp --- a/src/hotspot/share/oops/instanceKlass.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/oops/instanceKlass.cpp Fri May 17 08:29:55 2019 -0700 @@ -451,7 +451,7 @@ assert(is_instance_klass(), "is layout incorrect?"); assert(size_helper() == parser.layout_size(), "incorrect size_helper?"); - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { SystemDictionaryShared::init_dumptime_info(this); } } @@ -601,7 +601,7 @@ } set_annotations(NULL); - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { SystemDictionaryShared::remove_dumptime_info(this); } } @@ -2225,8 +2225,8 @@ // (1) We are running AOT to generate a shared library. return true; } - if (DumpSharedSpaces) { - // (2) We are running -Xshare:dump to create a shared archive + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { + // (2) We are running -Xshare:dump or -XX:ArchiveClassesAtExit to create a shared archive return true; } if (UseAOT && is_unsafe_anonymous) { @@ -2346,15 +2346,13 @@ array_klasses()->remove_unshareable_info(); } - // These are not allocated from metaspace, but they should should all be empty - // during dump time, so we don't need to worry about them in InstanceKlass::iterate(). - guarantee(_source_debug_extension == NULL, "must be"); - guarantee(_dep_context == NULL, "must be"); - guarantee(_osr_nmethods_head == NULL, "must be"); - + // These are not allocated from metaspace. They are safe to set to NULL. + _source_debug_extension = NULL; + _dep_context = NULL; + _osr_nmethods_head = NULL; #if INCLUDE_JVMTI - guarantee(_breakpoints == NULL, "must be"); - guarantee(_previous_versions == NULL, "must be"); + _breakpoints = NULL; + _previous_versions = NULL; _cached_class_file = NULL; #endif @@ -2475,6 +2473,10 @@ // notify ClassLoadingService of class unload ClassLoadingService::notify_class_unloaded(ik); + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { + SystemDictionaryShared::remove_dumptime_info(ik); + } + if (log_is_enabled(Info, class, unload)) { ResourceMark rm; log_info(class, unload)("unloading class %s " INTPTR_FORMAT, ik->external_name(), p2i(ik)); @@ -3422,7 +3424,12 @@ info_stream.print(" source: %s", class_loader->klass()->external_name()); } } else { - info_stream.print(" source: shared objects file"); + assert(this->is_shared(), "must be"); + if (MetaspaceShared::is_shared_dynamic((void*)this)) { + info_stream.print(" source: shared objects file (top)"); + } else { + info_stream.print(" source: shared objects file"); + } } msg.info("%s", info_stream.as_string()); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/oops/klass.cpp --- a/src/hotspot/share/oops/klass.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/oops/klass.cpp Fri May 17 08:29:55 2019 -0700 @@ -525,7 +525,8 @@ } void Klass::remove_unshareable_info() { - assert (DumpSharedSpaces, "only called for DumpSharedSpaces"); + assert (DumpSharedSpaces || DynamicDumpSharedSpaces, + "only called during CDS dump time"); JFR_ONLY(REMOVE_ID(this);) if (log_is_enabled(Trace, cds, unshareable)) { ResourceMark rm; @@ -542,7 +543,7 @@ } void Klass::remove_java_mirror() { - assert (DumpSharedSpaces, "only called for DumpSharedSpaces"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "only called during CDS dump time"); if (log_is_enabled(Trace, cds, unshareable)) { ResourceMark rm; log_trace(cds, unshareable)("remove java_mirror: %s", external_name()); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/oops/method.cpp --- a/src/hotspot/share/oops/method.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/oops/method.cpp Fri May 17 08:29:55 2019 -0700 @@ -958,23 +958,30 @@ void Method::unlink_method() { _code = NULL; - assert(DumpSharedSpaces, "dump time only"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, "dump time only"); // Set the values to what they should be at run time. Note that // this Method can no longer be executed during dump time. _i2i_entry = Interpreter::entry_for_cds_method(this); _from_interpreted_entry = _i2i_entry; + if (DynamicDumpSharedSpaces) { + assert(_from_compiled_entry != NULL, "sanity"); + } else { + // TODO: Simplify the adapter trampoline allocation for static archiving. + // Remove the use of CDSAdapterHandlerEntry. + CDSAdapterHandlerEntry* cds_adapter = (CDSAdapterHandlerEntry*)adapter(); + constMethod()->set_adapter_trampoline(cds_adapter->get_adapter_trampoline()); + _from_compiled_entry = cds_adapter->get_c2i_entry_trampoline(); + assert(*((int*)_from_compiled_entry) == 0, + "must be NULL during dump time, to be initialized at run time"); + } + if (is_native()) { *native_function_addr() = NULL; set_signature_handler(NULL); } NOT_PRODUCT(set_compiled_invocation_count(0);) - CDSAdapterHandlerEntry* cds_adapter = (CDSAdapterHandlerEntry*)adapter(); - constMethod()->set_adapter_trampoline(cds_adapter->get_adapter_trampoline()); - _from_compiled_entry = cds_adapter->get_c2i_entry_trampoline(); - assert(*((int*)_from_compiled_entry) == 0, "must be NULL during dump time, to be initialized at run time"); - set_method_data(NULL); clear_method_counters(); } diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/oops/method.hpp --- a/src/hotspot/share/oops/method.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/oops/method.hpp Fri May 17 08:29:55 2019 -0700 @@ -468,9 +468,15 @@ void set_adapter_entry(AdapterHandlerEntry* adapter) { constMethod()->set_adapter_entry(adapter); } + void set_adapter_trampoline(AdapterHandlerEntry** trampoline) { + constMethod()->set_adapter_trampoline(trampoline); + } void update_adapter_trampoline(AdapterHandlerEntry* adapter) { constMethod()->update_adapter_trampoline(adapter); } + void set_from_compiled_entry(address entry) { + _from_compiled_entry = entry; + } address get_i2c_entry(); address get_c2i_entry(); @@ -511,7 +517,8 @@ address interpreter_entry() const { return _i2i_entry; } // Only used when first initialize so we can set _i2i_entry and _from_interpreted_entry void set_interpreter_entry(address entry) { - assert(!is_shared(), "shared method's interpreter entry should not be changed at run time"); + assert(!is_shared(), + "shared method's interpreter entry should not be changed at run time"); if (_i2i_entry != entry) { _i2i_entry = entry; } diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/oops/symbol.cpp --- a/src/hotspot/share/oops/symbol.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/oops/symbol.cpp Fri May 17 08:29:55 2019 -0700 @@ -74,6 +74,13 @@ FreeHeap(p); } +void Symbol::set_permanent() { + // This is called at a safepoint during dumping of a dynamic CDS archive. + assert(SafepointSynchronize::is_at_safepoint(), "must be at a safepoint"); + _length_and_refcount = pack_length_and_refcount(length(), PERM_REFCOUNT); +} + + // ------------------------------------------------------------------ // Symbol::starts_with // diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/oops/symbol.hpp --- a/src/hotspot/share/oops/symbol.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/oops/symbol.hpp Fri May 17 08:29:55 2019 -0700 @@ -169,6 +169,7 @@ bool is_permanent() { return (refcount() == PERM_REFCOUNT); } + void set_permanent(); void make_permanent(); // Function char_at() returns the Symbol's selected u1 byte as a char type. diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/prims/cdsoffsets.cpp --- a/src/hotspot/share/prims/cdsoffsets.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/prims/cdsoffsets.cpp Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -26,6 +26,7 @@ #include "utilities/macros.hpp" #if INCLUDE_CDS #include "runtime/os.hpp" +#include "memory/dynamicArchive.hpp" #include "memory/filemap.hpp" #include "memory/allocation.hpp" #include "memory/allocation.inline.hpp" @@ -44,6 +45,7 @@ #define CREATE_OFFSET_MAPS \ _all = new CDSOffsets("size_t_size", sizeof(size_t), NULL); \ + ADD_NEXT(_all, "int_size", sizeof(int)); \ ADD_NEXT(_all, "FileMapHeader::_magic", offset_of(FileMapHeader, _magic)); \ ADD_NEXT(_all, "FileMapHeader::_crc", offset_of(FileMapHeader, _crc)); \ ADD_NEXT(_all, "FileMapHeader::_version", offset_of(FileMapHeader, _version)); \ @@ -52,6 +54,7 @@ ADD_NEXT(_all, "CDSFileMapRegion::_used", offset_of(CDSFileMapRegion, _used)); \ ADD_NEXT(_all, "FileMapHeader::_paths_misc_info_size", offset_of(FileMapHeader, _paths_misc_info_size)); \ ADD_NEXT(_all, "file_header_size", sizeof(FileMapHeader)); \ + ADD_NEXT(_all, "DynamicArchiveHeader::_base_archive_crc", offset_of(DynamicArchiveHeader, _base_archive_crc)); \ ADD_NEXT(_all, "CDSFileMapRegion_size", sizeof(CDSFileMapRegion)); int CDSOffsets::find_offset(const char* name) { diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/prims/whitebox.cpp --- a/src/hotspot/share/prims/whitebox.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/prims/whitebox.cpp Fri May 17 08:29:55 2019 -0700 @@ -38,6 +38,7 @@ #include "gc/shared/genArguments.hpp" #include "gc/shared/genCollectedHeap.hpp" #include "jvmtifiles/jvmtiEnv.hpp" +#include "memory/filemap.hpp" #include "memory/heapShared.inline.hpp" #include "memory/metaspaceShared.hpp" #include "memory/metadataFactory.hpp" @@ -1883,6 +1884,10 @@ return UseSharedSpaces; WB_END +WB_ENTRY(jboolean, WB_CDSMemoryMappingFailed(JNIEnv* env, jobject wb)) + return FileMapInfo::memory_mapping_failed(); +WB_END + WB_ENTRY(jboolean, WB_IsShared(JNIEnv* env, jobject wb, jobject obj)) oop obj_oop = JNIHandles::resolve(obj); return HeapShared::is_archived_object(obj_oop); @@ -1908,6 +1913,15 @@ } WB_END +WB_ENTRY(void, WB_LinkClass(JNIEnv* env, jobject wb, jclass clazz)) + Klass *k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)); + if (!k->is_instance_klass()) { + return; + } + InstanceKlass *ik = InstanceKlass::cast(k); + ik->link_class(THREAD); // may throw verification error +WB_END + WB_ENTRY(jboolean, WB_AreOpenArchiveHeapObjectsMapped(JNIEnv* env)) return HeapShared::open_archive_heap_region_mapped(); WB_END @@ -2342,10 +2356,12 @@ {CC"isSharedClass", CC"(Ljava/lang/Class;)Z", (void*)&WB_IsSharedClass }, {CC"areSharedStringsIgnored", CC"()Z", (void*)&WB_AreSharedStringsIgnored }, {CC"getResolvedReferences", CC"(Ljava/lang/Class;)Ljava/lang/Object;", (void*)&WB_GetResolvedReferences}, + {CC"linkClass", CC"(Ljava/lang/Class;)V", (void*)&WB_LinkClass}, {CC"areOpenArchiveHeapObjectsMapped", CC"()Z", (void*)&WB_AreOpenArchiveHeapObjectsMapped}, {CC"isCDSIncludedInVmBuild", CC"()Z", (void*)&WB_IsCDSIncludedInVmBuild }, {CC"isJFRIncludedInVmBuild", CC"()Z", (void*)&WB_IsJFRIncludedInVmBuild }, - {CC"isJavaHeapArchiveSupported", CC"()Z", (void*)&WB_IsJavaHeapArchiveSupported }, + {CC"isJavaHeapArchiveSupported", CC"()Z", (void*)&WB_IsJavaHeapArchiveSupported }, + {CC"cdsMemoryMappingFailed", CC"()Z", (void*)&WB_CDSMemoryMappingFailed }, {CC"clearInlineCaches0", CC"(Z)V", (void*)&WB_ClearInlineCaches }, {CC"handshakeWalkStack", CC"(Ljava/lang/Thread;Z)I", (void*)&WB_HandshakeWalkStack }, diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/runtime/arguments.cpp --- a/src/hotspot/share/runtime/arguments.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/runtime/arguments.cpp Fri May 17 08:29:55 2019 -0700 @@ -36,6 +36,7 @@ #include "logging/logStream.hpp" #include "logging/logTag.hpp" #include "memory/allocation.inline.hpp" +#include "memory/filemap.hpp" #include "oops/oop.inline.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/arguments.hpp" @@ -95,6 +96,7 @@ bool Arguments::_enable_preview = false; char* Arguments::SharedArchivePath = NULL; +char* Arguments::SharedDynamicArchivePath = NULL; AgentLibraryList Arguments::_libraryList; AgentLibraryList Arguments::_agentList; @@ -1469,7 +1471,8 @@ "--patch-module" }; void Arguments::check_unsupported_dumping_properties() { - assert(DumpSharedSpaces, "this function is only used with -Xshare:dump"); + assert(DumpSharedSpaces || DynamicDumpSharedSpaces, + "this function is only used with CDS dump time"); assert(ARRAY_SIZE(unsupported_properties) == ARRAY_SIZE(unsupported_options), "must be"); // If a vm option is found in the unsupported_options array, vm will exit with an error message. SystemProperty* sp = system_properties(); @@ -1492,6 +1495,13 @@ bool Arguments::check_unsupported_cds_runtime_properties() { assert(UseSharedSpaces, "this function is only used with -Xshare:{on,auto}"); assert(ARRAY_SIZE(unsupported_properties) == ARRAY_SIZE(unsupported_options), "must be"); + if (ArchiveClassesAtExit != NULL) { + // dynamic dumping, just return false for now. + // check_unsupported_dumping_properties() will be called later to check the same set of + // properties, and will exit the VM with the correct error message if the unsupported properties + // are used. + return false; + } for (uint i = 0; i < ARRAY_SIZE(unsupported_properties); i++) { if (get_property(unsupported_properties[i]) != NULL) { if (RequireSharedSpaces) { @@ -2713,7 +2723,6 @@ if (FLAG_SET_CMDLINE(bool, DumpSharedSpaces, true) != JVMFlag::SUCCESS) { return JNI_EINVAL; } - set_mode_flags(_int); // Prevent compilation, which creates objects // -Xshare:on } else if (match_option(option, "-Xshare:on")) { if (FLAG_SET_CMDLINE(bool, UseSharedSpaces, true) != JVMFlag::SUCCESS) { @@ -2722,7 +2731,7 @@ if (FLAG_SET_CMDLINE(bool, RequireSharedSpaces, true) != JVMFlag::SUCCESS) { return JNI_EINVAL; } - // -Xshare:auto + // -Xshare:auto || -XX:ArchiveClassesAtExit= } else if (match_option(option, "-Xshare:auto")) { if (FLAG_SET_CMDLINE(bool, UseSharedSpaces, true) != JVMFlag::SUCCESS) { return JNI_EINVAL; @@ -3110,15 +3119,24 @@ // the archived Klasses and Java string objects (at dump time only). UseBiasedLocking = false; + // Compiler threads may concurrently update the class metadata (such as method entries), so it's + // unsafe with DumpSharedSpaces (which modifies the class metadata in place). Let's disable + // compiler just to be safe. + // + // Note: this is not a concern for DynamicDumpSharedSpaces, which makes a copy of the class metadata + // instead of modifying them in place. The copy is inaccessible to the compiler. + // TODO: revisit the following for the static archive case. + set_mode_flags(_int); + } + if (DumpSharedSpaces || ArchiveClassesAtExit != NULL) { // Always verify non-system classes during CDS dump if (!BytecodeVerificationRemote) { BytecodeVerificationRemote = true; log_info(cds)("All non-system classes will be verified (-Xverify:remote) during CDS dump time."); } - - // Compilation is already disabled if the user specifies -Xshare:dump. - // Disable compilation in case user specifies -XX:+DumpSharedSpaces instead of -Xshare:dump. - set_mode_flags(_int); + } + if (ArchiveClassesAtExit == NULL) { + FLAG_SET_DEFAULT(DynamicDumpSharedSpaces, false); } if (UseSharedSpaces && patch_mod_javabase) { no_shared_spaces("CDS is disabled when " JAVA_BASE_NAME " module is patched."); @@ -3427,6 +3445,7 @@ } } +#if INCLUDE_CDS // Sharing support // Construct the path to the archive char* Arguments::get_default_shared_archive_path() { @@ -3446,15 +3465,104 @@ return default_archive_path; } -static char* get_shared_archive_path() { - char *shared_archive_path; +int Arguments::num_archives(const char* archive_path) { + if (archive_path == NULL) { + return 0; + } + int npaths = 1; + char* p = (char*)archive_path; + while (*p != '\0') { + if (*p == os::path_separator()[0]) { + npaths++; + } + p++; + } + return npaths; +} + +void Arguments::extract_shared_archive_paths(const char* archive_path, + char** base_archive_path, + char** top_archive_path) { + char* begin_ptr = (char*)archive_path; + char* end_ptr = strchr((char*)archive_path, os::path_separator()[0]); + if (end_ptr == NULL || end_ptr == begin_ptr) { + vm_exit_during_initialization("Base archive was not specified", archive_path); + } + size_t len = end_ptr - begin_ptr; + char* cur_path = NEW_C_HEAP_ARRAY(char, len + 1, mtInternal); + strncpy(cur_path, begin_ptr, len); + cur_path[len] = '\0'; + FileMapInfo::check_archive((const char*)cur_path, true /*is_static*/); + *base_archive_path = cur_path; + + begin_ptr = ++end_ptr; + if (*begin_ptr == '\0') { + vm_exit_during_initialization("Top archive was not specified", archive_path); + } + end_ptr = strchr(begin_ptr, '\0'); + assert(end_ptr != NULL, "sanity"); + len = end_ptr - begin_ptr; + cur_path = NEW_C_HEAP_ARRAY(char, len + 1, mtInternal); + strncpy(cur_path, begin_ptr, len + 1); + //cur_path[len] = '\0'; + FileMapInfo::check_archive((const char*)cur_path, false /*is_static*/); + *top_archive_path = cur_path; +} + +bool Arguments::init_shared_archive_paths() { + if (ArchiveClassesAtExit != NULL) { + if (DumpSharedSpaces) { + vm_exit_during_initialization("-XX:ArchiveClassesAtExit cannot be used with -Xshare:dump"); + } + if (FLAG_SET_CMDLINE(bool, DynamicDumpSharedSpaces, true) != JVMFlag::SUCCESS) { + return false; + } + check_unsupported_dumping_properties(); + SharedDynamicArchivePath = os::strdup_check_oom(ArchiveClassesAtExit, mtArguments); + } if (SharedArchiveFile == NULL) { - shared_archive_path = Arguments::get_default_shared_archive_path(); + SharedArchivePath = get_default_shared_archive_path(); } else { - shared_archive_path = os::strdup_check_oom(SharedArchiveFile, mtArguments); + int archives = num_archives(SharedArchiveFile); + if (DynamicDumpSharedSpaces || DumpSharedSpaces) { + if (archives > 1) { + vm_exit_during_initialization( + "Cannot have more than 1 archive file specified in -XX:SharedArchiveFile during CDS dumping"); + } + if (DynamicDumpSharedSpaces) { + if (FileMapInfo::same_files(SharedArchiveFile, ArchiveClassesAtExit)) { + vm_exit_during_initialization( + "Cannot have the same archive file specified for -XX:SharedArchiveFile and -XX:ArchiveClassesAtExit", + SharedArchiveFile); + } + } + } + if (!DynamicDumpSharedSpaces && !DumpSharedSpaces){ + if (archives > 2) { + vm_exit_during_initialization( + "Cannot have more than 2 archive files specified in the -XX:SharedArchiveFile option"); + } + if (archives == 1) { + char* temp_archive_path = os::strdup_check_oom(SharedArchiveFile, mtArguments); + int name_size; + bool success = + FileMapInfo::get_base_archive_name_from_header(temp_archive_path, &name_size, &SharedArchivePath); + if (!success) { + SharedArchivePath = temp_archive_path; + } else { + SharedDynamicArchivePath = temp_archive_path; + } + } else { + extract_shared_archive_paths((const char*)SharedArchiveFile, + &SharedArchivePath, &SharedDynamicArchivePath); + } + } else { // CDS dumping + SharedArchivePath = os::strdup_check_oom(SharedArchiveFile, mtArguments); + } } - return shared_archive_path; + return (SharedArchivePath != NULL); } +#endif // INCLUDE_CDS #ifndef PRODUCT // Determine whether LogVMOutput should be implicitly turned on. @@ -3786,11 +3894,12 @@ return result; } - // Call get_shared_archive_path() here, after possible SharedArchiveFile option got parsed. - SharedArchivePath = get_shared_archive_path(); - if (SharedArchivePath == NULL) { +#if INCLUDE_CDS + // Initialize shared archive paths which could include both base and dynamic archive paths + if (!init_shared_archive_paths()) { return JNI_ENOMEM; } +#endif // Delay warning until here so that we've had a chance to process // the -XX:-PrintWarnings flag diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/runtime/arguments.hpp --- a/src/hotspot/share/runtime/arguments.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/runtime/arguments.hpp Fri May 17 08:29:55 2019 -0700 @@ -484,6 +484,11 @@ static AliasedLoggingFlag catch_logging_aliases(const char* name, bool on); static char* SharedArchivePath; + static char* SharedDynamicArchivePath; + static int num_archives(const char* archive_path) NOT_CDS_RETURN_(0); + static void extract_shared_archive_paths(const char* archive_path, + char** base_archive_path, + char** top_archive_path) NOT_CDS_RETURN; public: // Parses the arguments, first phase @@ -563,6 +568,7 @@ static vfprintf_hook_t vfprintf_hook() { return _vfprintf_hook; } static const char* GetSharedArchivePath() { return SharedArchivePath; } + static const char* GetSharedDynamicArchivePath() { return SharedDynamicArchivePath; } // Java launcher properties static void process_sun_java_launcher_properties(JavaVMInitArgs* args); @@ -625,7 +631,8 @@ static char* get_appclasspath() { return _java_class_path->value(); } static void fix_appclasspath(); - static char* get_default_shared_archive_path(); + static char* get_default_shared_archive_path() NOT_CDS_RETURN_(NULL); + static bool init_shared_archive_paths() NOT_CDS_RETURN_(false); // Operation modi static Mode mode() { return _mode; } diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/runtime/globals.hpp --- a/src/hotspot/share/runtime/globals.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/runtime/globals.hpp Fri May 17 08:29:55 2019 -0700 @@ -2359,6 +2359,9 @@ "shared spaces, and dumps the shared spaces to a file to be " \ "used in future JVM runs") \ \ + product(bool, DynamicDumpSharedSpaces, false, \ + "Dynamic archive") \ + \ product(bool, PrintSharedArchiveAndExit, false, \ "Print shared archive file contents") \ \ @@ -2476,6 +2479,9 @@ product(ccstr, SharedArchiveFile, NULL, \ "Override the default location of the CDS archive file") \ \ + product(ccstr, ArchiveClassesAtExit, NULL, \ + "The path and name of the dynamic archive file") \ + \ product(ccstr, ExtraSharedClassListFile, NULL, \ "Extra classlist for building the CDS archive file") \ \ diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/runtime/java.cpp --- a/src/hotspot/share/runtime/java.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/runtime/java.cpp Fri May 17 08:29:55 2019 -0700 @@ -43,6 +43,7 @@ #include "logging/logStream.hpp" #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" +#include "memory/dynamicArchive.hpp" #include "memory/universe.hpp" #include "oops/constantPool.hpp" #include "oops/generateOopMap.hpp" @@ -498,6 +499,12 @@ // Note: we don't wait until it actually dies. os::terminate_signal_thread(); +#if INCLUDE_CDS + if (DynamicDumpSharedSpaces) { + DynamicArchive::dump(); + } +#endif + print_statistics(); Universe::heap()->print_tracing_info(); diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/runtime/mutexLocker.cpp --- a/src/hotspot/share/runtime/mutexLocker.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/runtime/mutexLocker.cpp Fri May 17 08:29:55 2019 -0700 @@ -153,9 +153,12 @@ #if INCLUDE_NMT Mutex* NMTQuery_lock = NULL; #endif -#if INCLUDE_CDS && INCLUDE_JVMTI +#if INCLUDE_CDS +#if INCLUDE_JVMTI Mutex* CDSClassFileStream_lock = NULL; #endif +Mutex* DumpTimeTable_lock = NULL; +#endif // INCLUDE_CDS #if INCLUDE_JVMCI Monitor* JVMCI_lock = NULL; @@ -351,7 +354,8 @@ #if INCLUDE_NMT def(NMTQuery_lock , PaddedMutex , max_nonleaf, false, Monitor::_safepoint_check_always); #endif -#if INCLUDE_CDS && INCLUDE_JVMTI +#if INCLUDE_CDS +#if INCLUDE_JVMTI def(CDSClassFileStream_lock , PaddedMutex , max_nonleaf, false, Monitor::_safepoint_check_always); #endif @@ -360,6 +364,8 @@ def(JVMCIGlobalAlloc_lock , PaddedMutex , nonleaf, true, Monitor::_safepoint_check_never); def(JVMCIGlobalActive_lock , PaddedMutex , nonleaf-1, true, Monitor::_safepoint_check_never); #endif + def(DumpTimeTable_lock , PaddedMutex , leaf, true, Monitor::_safepoint_check_never); +#endif // INCLUDE_CDS } GCMutexLocker::GCMutexLocker(Monitor * mutex) { diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/runtime/mutexLocker.hpp --- a/src/hotspot/share/runtime/mutexLocker.hpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/runtime/mutexLocker.hpp Fri May 17 08:29:55 2019 -0700 @@ -132,9 +132,12 @@ #if INCLUDE_NMT extern Mutex* NMTQuery_lock; // serialize NMT Dcmd queries #endif -#if INCLUDE_CDS && INCLUDE_JVMTI +#if INCLUDE_CDS +#if INCLUDE_JVMTI extern Mutex* CDSClassFileStream_lock; // FileMapInfo::open_stream_for_jvmti #endif +extern Mutex* DumpTimeTable_lock; // SystemDictionaryShared::find_or_allocate_info_for +#endif // INCLUDE_CDS #if INCLUDE_JFR extern Mutex* JfrStacktrace_lock; // used to guard access to the JFR stacktrace table extern Monitor* JfrMsg_lock; // protects JFR messaging diff -r d4e7ccaf1445 -r 1512d88b24c6 src/hotspot/share/runtime/thread.cpp --- a/src/hotspot/share/runtime/thread.cpp Fri May 17 10:48:02 2019 -0400 +++ b/src/hotspot/share/runtime/thread.cpp Fri May 17 08:29:55 2019 -0700 @@ -3964,10 +3964,8 @@ SystemDictionary::compute_java_loaders(CHECK_JNI_ERR); #if INCLUDE_CDS - if (DumpSharedSpaces) { - // capture the module path info from the ModuleEntryTable - ClassLoader::initialize_module_path(THREAD); - } + // capture the module path info from the ModuleEntryTable + ClassLoader::initialize_module_path(THREAD); #endif #if INCLUDE_JVMCI @@ -4169,7 +4167,7 @@ for (agent = Arguments::agents(); agent != NULL; agent = agent->next()) { // CDS dumping does not support native JVMTI agent. // CDS dumping supports Java agent if the AllowArchivingWithJavaAgent diagnostic option is specified. - if (DumpSharedSpaces) { + if (DumpSharedSpaces || DynamicDumpSharedSpaces) { if(!agent->is_instrument_lib()) { vm_exit_during_cds_dumping("CDS dumping does not support native JVMTI agent, name", agent->name()); } else if (!AllowArchivingWithJavaAgent) { diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/TEST.groups --- a/test/hotspot/jtreg/TEST.groups Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/TEST.groups Fri May 17 08:29:55 2019 -0700 @@ -314,6 +314,23 @@ hotspot_appcds = \ runtime/appcds/ +hotspot_appcds_dynamic = \ + runtime/appcds/ \ + -runtime/appcds/cacheObject \ + -runtime/appcds/customLoader \ + -runtime/appcds/dynamicArchive \ + -runtime/appcds/javaldr/ArrayTest.java \ + -runtime/appcds/javaldr/GCSharedStringsDuringDump.java \ + -runtime/appcds/javaldr/HumongousDuringDump.java \ + -runtime/appcds/sharedStrings \ + -runtime/appcds/DumpClassList.java \ + -runtime/appcds/ExtraSymbols.java \ + -runtime/appcds/LongClassListPath.java \ + -runtime/appcds/LotsOfClasses.java \ + -runtime/appcds/SharedArchiveConsistency.java \ + -runtime/appcds/UnusedCPDuringDump.java \ + -runtime/appcds/VerifierTest_1B.java + # A subset of AppCDS tests to be run in tier1 tier1_runtime_appcds = \ runtime/appcds/HelloTest.java \ diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/AppendClasspath.java --- a/test/hotspot/jtreg/runtime/appcds/AppendClasspath.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/AppendClasspath.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -58,23 +58,26 @@ // FAIL: 2) runtime with classpath different from the one used in dump time // (runtime has an extra jar file prepended to the class path) TestCommon.run( + "-Xlog:cds", "-cp", appJar2 + File.pathSeparator + appJar, "HelloMore") - .assertAbnormalExit(errorMessage1, errorMessage2); + .assertAbnormalExit(errorMessage1, errorMessage2); // FAIL: 3) runtime with classpath part of the one used in dump time TestCommon.testDump(appJar + File.pathSeparator + appJar2, TestCommon.list("Hello")); TestCommon.run( + "-Xlog:cds", "-cp", appJar2, "Hello") - .assertAbnormalExit(errorMessage1, errorMessage2); + .assertAbnormalExit(errorMessage1, errorMessage2); // FAIL: 4) runtime with same set of jar files in the classpath but // with different order TestCommon.run( + "-Xlog:cds", "-cp", appJar2 + File.pathSeparator + appJar, "HelloMore") - .assertAbnormalExit(errorMessage1, errorMessage2); + .assertAbnormalExit(errorMessage1, errorMessage2); } } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java --- a/test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/BootClassPathMismatch.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -55,7 +55,11 @@ test.testBootClassPathMismatch(); test.testBootClassPathMismatchWithAppClass(); test.testBootClassPathMismatchWithBadPath(); - test.testBootClassPathMatchWithAppend(); + if (!TestCommon.isDynamicArchive()) { + // this test is not applicable to dynamic archive since + // there is no class to be archived in the top archive + test.testBootClassPathMatchWithAppend(); + } test.testBootClassPathMatch(); } @@ -77,11 +81,13 @@ TestCommon.dump(appJar, appClasses, "-Xbootclasspath/a:" + appJar); TestCommon.run( + "-Xlog:cds", "-cp", appJar, "-Xbootclasspath/a:" + otherJar, "Hello") .assertAbnormalExit(mismatchMessage); TestCommon.dump(appJar, appClasses, "-Xbootclasspath/a:" + otherJar); TestCommon.run( + "-Xlog:cds", "-cp", appJar, "-Xbootclasspath/a:" + appJar, "Hello") .assertAbnormalExit(mismatchMessage); } @@ -100,6 +106,7 @@ TestCommon.dump(appJar, appClasses, "-Xbootclasspath/a:" + appJar); TestCommon.run( + "-Xlog:cds", "-cp", appJar, "-Xbootclasspath/a:" + otherJar, "Hello") .assertAbnormalExit(mismatchMessage); } @@ -148,6 +155,7 @@ String appClasses[] = {"Hello"}; TestCommon.dump(appJar, appClasses); TestCommon.run( + "-Xlog:cds", "-cp", appJar, "-Xbootclasspath/a:" + appJar, "Hello") .assertAbnormalExit(mismatchMessage); } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/CDSandJFR.java --- a/test/hotspot/jtreg/runtime/appcds/CDSandJFR.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/CDSandJFR.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, 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 @@ -30,7 +30,7 @@ * @modules jdk.jfr * @build Hello GetFlightRecorder * @run driver ClassFileInstaller -jar CDSandJFR.jar Hello GetFlightRecorder GetFlightRecorder$TestEvent GetFlightRecorder$SimpleEvent - * @run driver/timeout=500 CDSandJFR + * @run main/othervm/timeout=500 CDSandJFR */ import jdk.test.lib.BuildHelper; diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/CaseSensitiveClassPath.java --- a/test/hotspot/jtreg/runtime/appcds/CaseSensitiveClassPath.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/CaseSensitiveClassPath.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2019, 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 @@ -78,8 +78,7 @@ } boolean isSameFile = Files.isSameFile(jarPath, jarPathUpper); - TestCommon.run("-cp", appJarUpper, "Hello", "-Xlog:class+path=info", - "-Xlog:cds") + TestCommon.run("-Xlog:class+path=info,cds", "-cp", appJarUpper, "Hello") .ifNoMappingFailure(output -> { if (isSameFile) { output.shouldContain("Hello World"); diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/CommandLineFlagCombo.java --- a/test/hotspot/jtreg/runtime/appcds/CommandLineFlagCombo.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/CommandLineFlagCombo.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -72,10 +72,22 @@ continue; OutputAnalyzer dumpOutput = TestCommon.dump(appJar, classList, testEntry); - TestCommon.checkDump(dumpOutput, "Loading classes to share"); + if (!TestCommon.isDynamicArchive()) { + TestCommon.checkDump(dumpOutput, "Loading classes to share"); + } else { + if (testEntry.contains("ObjectAlignmentInBytes")) { + dumpOutput.shouldHaveExitValue(1) + .shouldMatch("The shared archive file's ObjectAlignmentInBytes of .* does not equal the current ObjectAlignmentInBytes of"); + } else { + TestCommon.checkDump(dumpOutput, "Loading classes to share"); + } + } - OutputAnalyzer execOutput = TestCommon.exec(appJar, testEntry, "Hello"); - TestCommon.checkExec(execOutput, "Hello World"); + if ((TestCommon.isDynamicArchive() && !testEntry.contains("ObjectAlignmentInBytes")) || + !TestCommon.isDynamicArchive()) { + OutputAnalyzer execOutput = TestCommon.exec(appJar, testEntry, "Hello"); + TestCommon.checkExec(execOutput, "Hello World"); + } } for (int i=0; i<2; i++) { diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/CommandLineFlagComboNegative.java --- a/test/hotspot/jtreg/runtime/appcds/CommandLineFlagComboNegative.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/CommandLineFlagComboNegative.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -65,8 +65,10 @@ if (Platform.is64bit()) { testTable.add( new TestVector("-XX:ObjectAlignmentInBytes=8", "-XX:ObjectAlignmentInBytes=16", "An error has occurred while processing the shared archive file", 1) ); - testTable.add( new TestVector("-XX:ObjectAlignmentInBytes=64", "-XX:ObjectAlignmentInBytes=32", - "An error has occurred while processing the shared archive file", 1) ); + if (!TestCommon.isDynamicArchive()) { + testTable.add( new TestVector("-XX:ObjectAlignmentInBytes=64", "-XX:ObjectAlignmentInBytes=32", + "An error has occurred while processing the shared archive file", 1) ); + } testTable.add( new TestVector("-XX:+UseCompressedOops", "-XX:-UseCompressedOops", "Class data sharing is inconsistent with other specified options", 1) ); testTable.add( new TestVector("-XX:+UseCompressedClassPointers", "-XX:-UseCompressedClassPointers", diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/DirClasspathTest.java --- a/test/hotspot/jtreg/runtime/appcds/DirClasspathTest.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/DirClasspathTest.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019, 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 @@ -27,7 +27,9 @@ * @summary Handling of directories in -cp is based on the classlist * @requires vm.cds * @library /test/lib + * @modules jdk.jartool/sun.tools.jar * @compile test-classes/Hello.java + * @compile test-classes/Super.java * @run driver DirClasspathTest */ @@ -42,11 +44,19 @@ public class DirClasspathTest { private static final int MAX_PATH = 260; + // We add helloJar into the classpath to be compatible with TestCommon.DYNAMIC_DUMP + static OutputAnalyzer doDump(String path, String classList[], + String... suffix) throws Exception { + String helloJar = JarBuilder.getOrCreateHelloJar(); + return TestCommon.dump(helloJar + File.pathSeparator + path, classList, suffix); + } + public static void main(String[] args) throws Exception { File dir = new File(System.getProperty("user.dir")); File emptydir = new File(dir, "emptydir"); emptydir.mkdir(); + ///////////////////////////////////////////////////////////////// // The classlist only contains boot class in following test cases ///////////////////////////////////////////////////////////////// @@ -54,7 +64,7 @@ // Empty dir in -cp: should be OK OutputAnalyzer output; - output = TestCommon.dump(emptydir.getPath(), bootClassList, "-Xlog:class+path=info"); + output = doDump(emptydir.getPath(), bootClassList, "-Xlog:class+path=info"); TestCommon.checkDump(output); // Long path to empty dir in -cp: should be OK @@ -71,17 +81,17 @@ longDir.mkdir(); File subDir = new File(longDir, "subdir"); subDir.mkdir(); - output = TestCommon.dump(subDir.getPath(), bootClassList, "-Xlog:class+path=info"); + output = doDump(subDir.getPath(), bootClassList, "-Xlog:class+path=info"); TestCommon.checkDump(output); // Non-empty dir in -cp: should be OK // is not empty because it has at least one subdirectory, i.e., - output = TestCommon.dump(dir.getPath(), bootClassList, "-Xlog:class+path=info"); + output = doDump(dir.getPath(), bootClassList, "-Xlog:class+path=info"); TestCommon.checkDump(output); // Long path to non-empty dir in -cp: should be OK // is not empty because it has at least one subdirectory, i.e., - output = TestCommon.dump(longDir.getPath(), bootClassList, "-Xlog:class+path=info"); + output = doDump(longDir.getPath(), bootClassList, "-Xlog:class+path=info"); TestCommon.checkDump(output); ///////////////////////////////////////////////////////////////// @@ -90,27 +100,27 @@ String appClassList[] = {"java/lang/Object", "com/sun/tools/javac/Main"}; // Non-empty dir in -cp: should be OK (as long as no classes were loaded from there) - output = TestCommon.dump(dir.getPath(), appClassList, "-Xlog:class+path=info"); + output = doDump(dir.getPath(), appClassList, "-Xlog:class+path=info"); TestCommon.checkDump(output); // Long path to non-empty dir in -cp: should be OK (as long as no classes were loaded from there) - output = TestCommon.dump(longDir.getPath(), appClassList, "-Xlog:class+path=info"); + output = doDump(longDir.getPath(), appClassList, "-Xlog:class+path=info"); TestCommon.checkDump(output); ///////////////////////////////////////////////////////////////// // Loading an app class from a directory ///////////////////////////////////////////////////////////////// - String appClassList2[] = {"Hello", "java/lang/Object", "com/sun/tools/javac/Main"}; + String appClassList2[] = {"Super", "java/lang/Object", "com/sun/tools/javac/Main"}; // Non-empty dir in -cp: should report error if a class is loaded from it - output = TestCommon.dump(classDir.toString(), appClassList2, "-Xlog:class+path=info"); + output = doDump(classDir.toString(), appClassList2, "-Xlog:class+path=info,class+load=trace"); output.shouldNotHaveExitValue(0); output.shouldContain("Cannot have non-empty directory in paths"); // Long path to non-empty dir in -cp: should report error if a class is loaded from it - File srcClass = new File(classDir.toFile(), "Hello.class"); - File destClass = new File(longDir, "Hello.class"); + File srcClass = new File(classDir.toFile(), "Super.class"); + File destClass = new File(longDir, "Super.class"); Files.copy(srcClass.toPath(), destClass.toPath()); - output = TestCommon.dump(longDir.getPath(), appClassList2, "-Xlog:class+path=info"); + output = doDump(longDir.getPath(), appClassList2, "-Xlog:class+path=info"); output.shouldNotHaveExitValue(0); output.shouldContain("Cannot have non-empty directory in paths"); } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/JvmtiAddPath.java --- a/test/hotspot/jtreg/runtime/appcds/JvmtiAddPath.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/JvmtiAddPath.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -84,7 +84,9 @@ run(check_appcds_enabled, appJar, "-Xlog:class+load", "JvmtiApp", "noadd"); // appcds should be enabled System.out.println("Test case 2: add to boot classpath only - should find Hello.class in boot loader"); - run(check_appcds_disabled, appJar, "-Xlog:class+load", "JvmtiApp", "bootonly", addbootJar); // appcds should be disabled + String[] toCheck = (TestCommon.isDynamicArchive()) ? check_appcds_enabled + : check_appcds_disabled; + run(toCheck, appJar, "-Xlog:class+load", "JvmtiApp", "bootonly", addbootJar); // appcds should be disabled System.out.println("Test case 3: add to app classpath only - should find Hello.class in app loader"); run(appJar, "JvmtiApp", "apponly", addappJar); @@ -97,7 +99,11 @@ System.out.println("Test case 6: add to app using AppCDS, but add to boot using JVMTI - should find Hello.class in boot loader"); TestCommon.testDump(twoAppJars, TestCommon.list("JvmtiApp", "ExtraClass", "Hello"), use_whitebox_jar); - run(twoAppJars, "JvmtiApp", "bootonly", addappJar); + if (!TestCommon.isDynamicArchive()) { + // skip for dynamic archive, the Hello class will be loaded from + // the dynamic archive + run(twoAppJars, "JvmtiApp", "bootonly", addappJar); + } System.out.println("Test case 7: add to app using AppCDS, no JVMTI calls - should find Hello.class in app loader"); run(twoAppJars, "JvmtiApp", "noadd-appcds"); diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/LotsOfClasses.java --- a/test/hotspot/jtreg/runtime/appcds/LotsOfClasses.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/LotsOfClasses.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, 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 @@ -22,15 +22,7 @@ * */ -import java.net.URI; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Path; import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.cds.CDSOptions; @@ -41,16 +33,15 @@ * @summary Try to archive lots of classes by searching for classes from the jrt:/ file system. With JDK 12 * this will produce an archive with over 30,000 classes. * @requires vm.cds - * @library /test/lib + * @library /test/lib /test/hotspot/jtreg/runtime/appcds * @run driver/timeout=500 LotsOfClasses */ public class LotsOfClasses { - static Pattern pattern; public static void main(String[] args) throws Throwable { ArrayList list = new ArrayList<>(); - findAllClasses(list); + TestCommon.findAllClasses(list); CDSOptions opts = new CDSOptions(); opts.setClassList(list); @@ -64,28 +55,4 @@ OutputAnalyzer out = CDSTestUtils.createArchive(opts); CDSTestUtils.checkDump(out); } - - static void findAllClasses(ArrayList list) throws Throwable { - // Find all the classes in the jrt file system - pattern = Pattern.compile("/modules/[a-z.]*[a-z]+/([^-]*)[.]class"); - FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); - Path base = fs.getPath("/modules/"); - find(base, list); - } - - static void find(Path p, ArrayList list) throws Throwable { - try (DirectoryStream stream = Files.newDirectoryStream(p)) { - for (Path entry: stream) { - Matcher matcher = pattern.matcher(entry.toString()); - if (matcher.find()) { - String className = matcher.group(1); - list.add(className); - //System.out.println(className); - } - try { - find(entry, list); - } catch (Throwable t) {} - } - } - } } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/PackageSealing.java --- a/test/hotspot/jtreg/runtime/appcds/PackageSealing.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/PackageSealing.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -27,14 +27,15 @@ * @summary AppCDS handling of package. * @requires vm.cds * @library /test/lib - * @modules java.base/jdk.internal.misc - * java.management + * @modules jdk.jartool/sun.tools.jar * @compile test-classes/C1.java * @compile test-classes/C2.java * @compile test-classes/PackageSealingTest.java + * @compile test-classes/Hello.java * @run driver PackageSealing */ +import java.io.File; import jdk.test.lib.process.OutputAnalyzer; public class PackageSealing { @@ -44,16 +45,19 @@ ClassFileInstaller.Manifest.fromSourceFile("test-classes/package_seal.mf"), "PackageSealingTest", "sealed/pkg/C1", "pkg/C2"); + String helloJar = JarBuilder.getOrCreateHelloJar(); + String jars = helloJar + File.pathSeparator + appJar; + // test shared package from -cp path - TestCommon.testDump(appJar, TestCommon.list(classList)); + TestCommon.testDump(jars, TestCommon.list(classList)); OutputAnalyzer output; - output = TestCommon.exec(appJar, "PackageSealingTest"); + output = TestCommon.exec(jars, "PackageSealingTest"); TestCommon.checkExec(output, "OK"); // test shared package from -Xbootclasspath/a - TestCommon.dump(appJar, TestCommon.list(classList), + TestCommon.dump(helloJar, TestCommon.list(classList), "-Xbootclasspath/a:" + appJar); - output = TestCommon.exec(appJar, "-Xbootclasspath/a:" + appJar, "PackageSealingTest"); + output = TestCommon.exec(helloJar, "-Xbootclasspath/a:" + appJar, "PackageSealingTest"); TestCommon.checkExec(output, "OK"); } } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/ProhibitedPackage.java --- a/test/hotspot/jtreg/runtime/appcds/ProhibitedPackage.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/ProhibitedPackage.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -27,9 +27,7 @@ * @summary AppCDS handling of prohibited package. * @requires vm.cds * @library /test/lib - * @modules java.base/jdk.internal.misc - * java.management - * jdk.jartool/sun.tools.jar + * @modules jdk.jartool/sun.tools.jar * @compile test-classes/ProhibitedHelper.java test-classes/Prohibited.jasm * @run driver ProhibitedPackage */ @@ -46,7 +44,8 @@ String appJar = TestCommon.getTestJar("prohibited_pkg.jar"); // Test support for customer loaders - if (Platform.areCustomLoadersSupportedForCDS()) { + if (Platform.areCustomLoadersSupportedForCDS() && + !TestCommon.isDynamicArchive()) { String classlist[] = new String[] { "java/lang/Object id: 1", "java/lang/Prohibited id: 2 super: 1 source: " + appJar diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/SharedArchiveConsistency.java --- a/test/hotspot/jtreg/runtime/appcds/SharedArchiveConsistency.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/SharedArchiveConsistency.java Fri May 17 08:29:55 2019 -0700 @@ -71,7 +71,18 @@ public static File jsa; // will be updated during test public static File orgJsaFile; // kept the original file not touched. - public static String[] shared_region_name = {"MiscCode", "ReadWrite", "ReadOnly", "MiscData"}; + // The following should be consistent with the enum in the C++ MetaspaceShared class + public static String[] shared_region_name = { + "mc", // MiscCode + "rw", // ReadWrite + "ro", // ReadOnly + "md", // MiscData + "first_closed_archive", + "last_closed_archive", + "first_open_archive", + "last_open_archive" + }; + public static int num_regions = shared_region_name.length; public static String[] matchMessages = { "Unable to use shared archive", @@ -102,10 +113,11 @@ return file_header_size; } // this is not real header size, it is struct size + int int_size = wb.getOffsetForName("int_size"); file_header_size = wb.getOffsetForName("file_header_size"); int offset_path_misc_info = wb.getOffsetForName("FileMapHeader::_paths_misc_info_size") - offset_magic; - int path_misc_info_size = (int)readInt(fc, offset_path_misc_info, size_t_size); + int path_misc_info_size = (int)readInt(fc, offset_path_misc_info, int_size); file_header_size += path_misc_info_size; //readInt(fc, offset_path_misc_info, size_t_size); System.out.println("offset_path_misc_info = " + offset_path_misc_info); System.out.println("path_misc_info_size = " + path_misc_info_size); @@ -157,25 +169,26 @@ public static void modifyJsaContentRandomly() throws Exception { FileChannel fc = getFileChannel(); - // corrupt random area in the data areas (MiscCode, ReadWrite, ReadOnly, MiscData) + // corrupt random area in the data areas long[] used = new long[num_regions]; // record used bytes long start0, start, end, off; int used_offset, path_info_size; int bufSize; - System.out.printf("%-12s%-12s%-12s%-12s%-12s\n", "Space Name", "Offset", "Used bytes", "Reg Start", "Random Offset"); + System.out.printf("%-24s%12s%12s%16s\n", "Space Name", "Used bytes", "Reg Start", "Random Offset"); start0 = getFileHeaderSize(fc); for (int i = 0; i < num_regions; i++) { - used_offset = sp_offset + CDSFileMapRegion_size * i + sp_used_offset; - // read 'used' - used[i] = readInt(fc, used_offset, size_t_size); + used[i] = get_region_used_size_aligned(fc, i); start = start0; for (int j = 0; j < i; j++) { start += align_up_page(used[j]); } end = start + used[i]; + if (start == end) { + continue; // Ignore empty regions + } off = getRandomBetween(start, end); - System.out.printf("%-12s%-12d%-12d%-12d%-12d\n", shared_region_name[i], used_offset, used[i], start, off); + System.out.printf("%-24s%12d%12d%16d\n", shared_region_name[i], used[i], start, off); if (end - off < 1024) { bufSize = (int)(end - off + 1); } else { @@ -189,34 +202,50 @@ } } - public static void modifyJsaContent() throws Exception { + static long get_region_used_size_aligned(FileChannel fc, int region) throws Exception { + long n = sp_offset + CDSFileMapRegion_size * region + sp_used_offset; + long alignment = WhiteBox.getWhiteBox().metaspaceReserveAlignment(); + long used = readInt(fc, n, size_t_size); + used = (used + alignment - 1) & ~(alignment - 1); + return used; + } + + public static boolean modifyJsaContent(int region) throws Exception { FileChannel fc = getFileChannel(); byte[] buf = new byte[4096]; ByteBuffer bbuf = ByteBuffer.wrap(buf); long total = 0L; - long used_offset = 0L; long[] used = new long[num_regions]; - System.out.printf("%-12s%-12s\n", "Space name", "Used bytes"); + System.out.printf("%-24s%12s\n", "Space name", "Used bytes"); for (int i = 0; i < num_regions; i++) { - used_offset = sp_offset + CDSFileMapRegion_size* i + sp_used_offset; - // read 'used' - used[i] = readInt(fc, used_offset, size_t_size); - System.out.printf("%-12s%-12d\n", shared_region_name[i], used[i]); + used[i] = get_region_used_size_aligned(fc, i); + System.out.printf("%-24s%12d\n", shared_region_name[i], used[i]); total += used[i]; } - System.out.printf("%-12s%-12d\n", "Total: ", total); - long corrupt_used_offset = getFileHeaderSize(fc); - System.out.println("Corrupt RO section, offset = " + corrupt_used_offset); - while (used_offset < used[0]) { - writeData(fc, corrupt_used_offset, bbuf); + System.out.printf("%-24s%12d\n", "Total: ", total); + long header_size = getFileHeaderSize(fc); + long region_start_offset = header_size; + for (int i=0; i cmd = new ArrayList(); startNewArchiveName(); @@ -122,23 +152,73 @@ if (opts.appJar != null) { cmd.add("-cp"); cmd.add(opts.appJar); + File jf = new File(opts.appJar); + if (DYNAMIC_DUMP && !jf.isDirectory()) { + patchJarForDynamicDump(opts.appJar); + } } else { cmd.add("-Djava.class.path="); } - cmd.add("-Xshare:dump"); - - if (opts.archiveName == null) + if (opts.archiveName == null) { opts.archiveName = getCurrentArchiveName(); - - cmd.add("-XX:SharedArchiveFile=" + opts.archiveName); - - if (opts.classList != null) { - File classListFile = makeClassList(opts.classList); - cmd.add("-XX:ExtraSharedClassListFile=" + classListFile.getPath()); } - for (String s : opts.suffix) cmd.add(s); + if (DYNAMIC_DUMP) { + cmd.add("-Xshare:on"); + cmd.add("-XX:ArchiveClassesAtExit=" + opts.archiveName); + + cmd.add("-Xlog:cds"); + cmd.add("-Xlog:cds+dynamic"); + boolean mainModuleSpecified = false; + boolean patchModuleSpecified = false; + for (String s : opts.suffix) { + if (s.length() == 0) { + continue; + } + if (s.equals("-m")) { + mainModuleSpecified = true; + } + if (s.startsWith("--patch-module=")) { + patchModuleSpecified = true; + } + cmd.add(s); + } + + if (opts.appJar != null) { + // classlist is supported only when we have a Jar file to patch (to insert + // cdsutils.DynamicDumpHelper) + if (opts.classList == null) { + throw new RuntimeException("test.dynamic.dump requires classList file"); + } + + if (!mainModuleSpecified && !patchModuleSpecified) { + cmd.add("cdsutils.DynamicDumpHelper"); + File classListFile = makeClassList(opts.classList); + cmd.add(classListFile.getPath()); + } + } else { + if (!mainModuleSpecified && !patchModuleSpecified) { + // If you have an empty classpath, you cannot specify a classlist! + if (opts.classList != null && opts.classList.length > 0) { + throw new RuntimeException("test.dynamic.dump not supported empty classpath with non-empty classlist"); + } + cmd.add("-version"); + } + } + } else { + // static dump + cmd.add("-Xshare:dump"); + cmd.add("-XX:SharedArchiveFile=" + opts.archiveName); + + if (opts.classList != null) { + File classListFile = makeClassList(opts.classList); + cmd.add("-XX:ExtraSharedClassListFile=" + classListFile.getPath()); + } + for (String s : opts.suffix) { + cmd.add(s); + } + } String[] cmdLine = cmd.toArray(new String[cmd.size()]); ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); @@ -154,6 +234,93 @@ // Some AppCDS tests are not compatible with this mode. See the group // hotspot_appcds_with_jfr in ../../TEST.ROOT for details. private static final boolean RUN_WITH_JFR = Boolean.getBoolean("test.cds.run.with.jfr"); + // This method simulates -Xshare:dump with -XX:ArchiveClassesAtExit. This way, we + // can re-use many tests (outside of the ./dynamicArchive directory) for testing + // general features of JDK-8215311 (JEP 350: Dynamic CDS Archives). + // + // We insert the cdsutils/DynamicDumpHelper.class into the first Jar file in + // the classpath. We use this class to load all the classes specified in the classlist. + // + // There's no need to change the run-time command-line: in this special mode, two + // archives are involved. The command-line specifies only the top archive. However, + // the location of the base archive is recorded in the top archive, so it can be + // determined by the JVM at runtime start-up. + // + // To run in this special mode, specify the following in your jtreg command-line + // -Dtest.dynamic.cds.archive=true + // + // Note that some tests are not compatible with this special mode, including + // + Tests in ./dynamicArchive: these tests are specifically written for + // dynamic archive, and do not use TestCommon.createArchive(), which works + // together with patchJarForDynamicDump(). + // + Tests related to cached objects and shared strings: dynamic dumping + // does not support these. + // + Custom loader tests: DynamicDumpHelper doesn't support the required + // classlist syntax. (FIXME). + // + Extra symbols and extra strings. + // See the hotspot_appcds_dynamic in ../../TEST.ROOT for details. + // + // To run all tests that are compatible with this mode: + // cd test/hotspot/jtreg + // jtreg -Dtest.dynamic.cds.archive=true :hotspot_appcds_dynamic + // + private static void patchJarForDynamicDump(String cp) throws Exception { + System.out.println("patchJarForDynamicDump: classpath = " + cp); + String firstJar = cp; + int n = firstJar.indexOf(File.pathSeparator); + if (n > 0) { + firstJar = firstJar.substring(0, n); + } + String classDir = System.getProperty("test.classes"); + String expected1 = classDir + File.separator; + String expected2 = System.getProperty("user.dir") + File.separator; + + if (!firstJar.startsWith(expected1) && !firstJar.startsWith(expected2)) { + throw new RuntimeException("FIXME: jar file not at a supported location ('" + + expected1 + "', or '" + expected2 + "'): " + firstJar); + } + + String replaceJar = firstJar + ".tmp"; + String patchClass = "cdsutils/DynamicDumpHelper.class"; + ZipFile zipFile = new ZipFile(firstJar); + byte[] buf = new byte[1024]; + int len; + if (zipFile.getEntry(patchClass) == null) { + FileOutputStream fout = new FileOutputStream(replaceJar); + final ZipOutputStream zos = new ZipOutputStream(fout); + + zos.putNextEntry(new ZipEntry(patchClass)); + InputStream is = new FileInputStream(classDir + File.separator + patchClass); + while ((len = (is.read(buf))) > 0) { + zos.write(buf, 0, len); + } + zos.closeEntry(); + is.close(); + + for (Enumeration e = zipFile.entries(); e.hasMoreElements(); ) { + ZipEntry entryIn = (ZipEntry) e.nextElement(); + zos.putNextEntry(entryIn); + is = zipFile.getInputStream(entryIn); + while ((len = is.read(buf)) > 0) { + zos.write(buf, 0, len); + } + zos.closeEntry(); + is.close(); + } + + zos.close(); + fout.close(); + zipFile.close(); + + File oldFile = new File(firstJar); + File newFile = new File(replaceJar); + oldFile.delete(); + newFile.renameTo(oldFile); + System.out.println("firstJar = " + firstJar + " Modified"); + } else { + System.out.println("firstJar = " + firstJar); + } + } // Execute JVM using AppCDS archive with specified AppCDSOptions public static OutputAnalyzer runWithArchive(AppCDSOptions opts) @@ -260,12 +427,18 @@ return runWithArchive(opts); } - // A common operation: dump, then check results public static OutputAnalyzer testDump(String appJar, String classList[], String... suffix) throws Exception { OutputAnalyzer output = dump(appJar, classList, suffix); - output.shouldContain("Loading classes to share"); + if (DYNAMIC_DUMP) { + if (isUnableToMap(output)) { + throw new SkippedException(UnableToMapMsg); + } + output.shouldContain("Written dynamic archive"); + } else { + output.shouldContain("Loading classes to share"); + } output.shouldHaveExitValue(0); return output; } @@ -301,7 +474,6 @@ return output; } - // Convenience concatenation utils public static String[] list(String ...args) { return args; @@ -336,6 +508,15 @@ return list.toArray(new String[list.size()]); } + public static String[] concat(String prefix, String[] extra) { + ArrayList list = new ArrayList(); + list.add(prefix); + for (String s : extra) { + list.add(s); + } + + return list.toArray(new String[list.size()]); + } // ===================== Concatenate paths public static String concatPaths(String... paths) { @@ -384,4 +565,29 @@ } return true; } + + static Pattern pattern; + + static void findAllClasses(ArrayList list) throws Throwable { + // Find all the classes in the jrt file system + pattern = Pattern.compile("/modules/[a-z.]*[a-z]+/([^-]*)[.]class"); + FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); + Path base = fs.getPath("/modules/"); + findAllClassesAtPath(base, list); + } + + private static void findAllClassesAtPath(Path p, ArrayList list) throws Throwable { + try (DirectoryStream stream = Files.newDirectoryStream(p)) { + for (Path entry: stream) { + Matcher matcher = pattern.matcher(entry.toString()); + if (matcher.find()) { + String className = matcher.group(1); + list.add(className); + } + try { + findAllClassesAtPath(entry, list); + } catch (Throwable t) {} + } + } + } } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/TraceLongClasspath.java --- a/test/hotspot/jtreg/runtime/appcds/TraceLongClasspath.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/TraceLongClasspath.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -31,6 +31,7 @@ * java.management * jdk.jartool/sun.tools.jar * @compile test-classes/Hello.java + * @compile test-classes/Super.java * @run driver TraceLongClasspath */ @@ -43,8 +44,10 @@ public static void main(String[] args) throws Exception { String appJar = JarBuilder.getOrCreateHelloJar(); + String dummyJar = JarBuilder.build("dummy", "Super"); String longClassPath = + dummyJar + ps + "/scratch/xxxx/yyyy/ZZZZZZ/aaaaaaaaaa/xx/abc/abc/modules/user-patch.jar" + ps + "/scratch/xxxx/yyyy/ZZZZZZ/aaaaaaaaaa/xx/abc/abc/modules/abc-startup.jar" + ps + "/scratch/xxxx/yyyy/ZZZZZZ/aaaaaaaaaa/xx/foobar_common/modules/features/com.foobar.db.jdbc7-dms.jar" + ps + @@ -91,15 +94,15 @@ // Then try to execute the archive with a different classpath and with -XX:+TraceClassPaths. // The diagnosis "expecting" app classpath trace should show the entire classpath. TestCommon.run( - "-XX:+TraceClassPaths", + "-XX:+TraceClassPaths", "-Xlog:cds", "-cp", appJar, "Hello") - .assertAbnormalExit(output -> { - output.shouldContain("Unable to use shared archive"); - output.shouldContain("shared class paths mismatch"); - // the "expecting" app classpath from -XX:+TraceClassPaths should not - // be truncated - output.shouldContain(myCP); - }); + .assertAbnormalExit(output -> { + output.shouldContain("Unable to use shared archive"); + output.shouldContain("shared class paths mismatch"); + // the "expecting" app classpath from -XX:+TraceClassPaths should not + // be truncated + output.shouldContain(myCP); + }); } } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/WrongClasspath.java --- a/test/hotspot/jtreg/runtime/appcds/WrongClasspath.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/WrongClasspath.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2019, 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 @@ -47,8 +47,9 @@ // Then try to execute the archive without -classpath -- it should fail TestCommon.run( /* "-cp", appJar, */ // <- uncomment this and the execution should succeed + "-Xlog:cds", "Hello") - .assertAbnormalExit("Unable to use shared archive", - "shared class paths mismatch"); + .assertAbnormalExit("Unable to use shared archive", + "shared class paths mismatch"); } } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/cdsutils/DynamicDumpHelper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/cdsutils/DynamicDumpHelper.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package cdsutils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +/** + * This class is used to simulate -Xshare:dump with -XX:ArchiveClassesAtExit. + * It loads all classes specified in a classlist file. See patchJarForDynamicDump() + * in ../TestCommon.java for details. + */ +public class DynamicDumpHelper { + public static void main(String args[]) throws Throwable { + File file = new File(args[0]); + + System.out.println("Loading classes to share..."); + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + String line; + while ((line = br.readLine()) != null) { + //System.out.println("Loading class: " + line); + line = line.replace('/', '.'); + try { + Class.forName(line); + } catch (java.lang.ClassNotFoundException ex) { + try { + Class.forName(line, true, null); + } catch (java.lang.ClassNotFoundException cnfe) { + System.out.println("Preload Warning: Cannot find " + line.replace('.', '/')); + } + } catch (Throwable t) { + System.out.println("Error: failed to load \"" + line + "\": " + t); + } + } + } + System.out.println("Loading classes to share: done."); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatA.java --- a/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatA.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatA.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -32,7 +32,7 @@ * @modules java.base/jdk.internal.misc * java.management * jdk.jartool/sun.tools.jar - * @compile test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java + * @compile ../test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java * test-classes/CustomInterface2_ia.java test-classes/CustomInterface2_ib.java * @run driver ClassListFormatA */ @@ -133,4 +133,3 @@ "input line too long (must be no longer than " + _max_allowed_line + " chars"); } } - diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatB.java --- a/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatB.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatB.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -32,7 +32,7 @@ * @modules java.base/jdk.internal.misc * java.management * jdk.jartool/sun.tools.jar - * @compile test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java + * @compile ../test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java * test-classes/CustomInterface2_ia.java test-classes/CustomInterface2_ib.java * @run driver ClassListFormatB */ @@ -69,4 +69,3 @@ "If source location is specified, id must be also specified"); } } - diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatBase.java --- a/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatBase.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatBase.java Fri May 17 08:29:55 2019 -0700 @@ -79,4 +79,3 @@ return TestCommon.list(args); } } - diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatC.java --- a/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatC.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatC.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -32,7 +32,7 @@ * @modules java.base/jdk.internal.misc * java.management * jdk.jartool/sun.tools.jar - * @compile test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java + * @compile ../test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java * test-classes/CustomInterface2_ia.java test-classes/CustomInterface2_ib.java * @run driver ClassListFormatC */ @@ -71,4 +71,3 @@ "If source location is not specified, interface(s) must not be specified"); } } - diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatD.java --- a/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatD.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatD.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -32,7 +32,7 @@ * @modules java.base/jdk.internal.misc * java.management * jdk.jartool/sun.tools.jar - * @compile test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java + * @compile ../test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java * test-classes/CustomInterface2_ia.java test-classes/CustomInterface2_ib.java * @run driver ClassListFormatD */ @@ -80,4 +80,3 @@ "Interface id 2 is not yet loaded"); } } - diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatE.java --- a/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatE.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/ClassListFormatE.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -32,7 +32,7 @@ * @modules java.base/jdk.internal.misc * java.management * jdk.jartool/sun.tools.jar - * @compile test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java + * @compile ../test-classes/Hello.java test-classes/CustomLoadee.java test-classes/CustomLoadee2.java * test-classes/CustomInterface2_ia.java test-classes/CustomInterface2_ib.java * @run driver ClassListFormatE */ @@ -106,4 +106,3 @@ "The specified super class CustomLoadee (id 4) does not match actual super class java.lang.Object"); } } - diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/HelloCustom.java --- a/test/hotspot/jtreg/runtime/appcds/customLoader/HelloCustom.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/HelloCustom.java Fri May 17 08:29:55 2019 -0700 @@ -27,10 +27,12 @@ * @summary Hello World test for AppCDS custom loader support * @requires vm.cds * @requires vm.cds.custom.loaders - * @library /test/lib /test/hotspot/jtreg/runtime/appcds - * @compile test-classes/Hello.java test-classes/CustomLoadee.java - * @build sun.hotspot.WhiteBox - * @run driver ClassFileInstaller -jar hello.jar Hello + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /runtime/testlibrary + * @modules java.base/jdk.internal.misc + * java.management + * @compile test-classes/HelloUnload.java test-classes/CustomLoadee.java + * @build sun.hotspot.WhiteBox ClassUnloadCommon + * @run driver ClassFileInstaller -jar hello.jar HelloUnload ClassUnloadCommon ClassUnloadCommon$1 ClassUnloadCommon$TestFailure * @run driver ClassFileInstaller -jar hello_custom.jar CustomLoadee * @run driver ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox * @run driver HelloCustom @@ -52,7 +54,7 @@ // Dump the archive String classlist[] = new String[] { - "Hello", + "HelloUnload", "java/lang/Object id: 1", "CustomLoadee id: 2 super: 1 source: " + customJarPath }; @@ -68,8 +70,7 @@ use_whitebox_jar, "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", - "Hello", customJarPath)); + "HelloUnload", customJarPath, "true", "true")); TestCommon.checkExec(output); } } - diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/HelloCustom_JFR.java --- a/test/hotspot/jtreg/runtime/appcds/customLoader/HelloCustom_JFR.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/HelloCustom_JFR.java Fri May 17 08:29:55 2019 -0700 @@ -30,10 +30,10 @@ * @requires vm.hasJFR * @requires vm.cds * @requires vm.cds.custom.loaders - * @library /test/lib /test/hotspot/jtreg/runtime/appcds - * @compile test-classes/Hello.java test-classes/CustomLoadee.java - * @build sun.hotspot.WhiteBox - * @run driver ClassFileInstaller -jar hello.jar Hello + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /runtime/testlibrary + * @compile test-classes/HelloUnload.java test-classes/CustomLoadee.java + * @build sun.hotspot.WhiteBox ClassUnloadCommon + * @run driver ClassFileInstaller -jar hello.jar HelloUnload ClassUnloadCommon ClassUnloadCommon$1 ClassUnloadCommon$TestFailure * @run driver ClassFileInstaller -jar hello_custom.jar CustomLoadee * @run driver ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox * @run driver HelloCustom_JFR @@ -47,4 +47,3 @@ HelloCustom.run("-XX:StartFlightRecording=dumponexit=true", "-Xlog:cds+jvmti=debug"); } } - diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/ProhibitedPackageNamesTest.java --- a/test/hotspot/jtreg/runtime/appcds/customLoader/ProhibitedPackageNamesTest.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/ProhibitedPackageNamesTest.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -31,7 +31,7 @@ * @modules java.base/jdk.internal.misc * java.management * jdk.jartool/sun.tools.jar - * @compile ClassListFormatBase.java test-classes/Hello.java test-classes/InProhibitedPkg.java + * @compile ClassListFormatBase.java ../test-classes/Hello.java test-classes/InProhibitedPkg.java * @run driver ProhibitedPackageNamesTest */ diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/customLoader/test-classes/HelloUnload.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/customLoader/test-classes/HelloUnload.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import sun.hotspot.WhiteBox; + +public class HelloUnload { + private static String className = "CustomLoadee"; + + public static void main(String args[]) throws Exception { + if (args.length != 3) { + throw new RuntimeException("Unexpected number of arguments: expected 3, actual " + args.length); + } + + String path = args[0]; + URL url = new File(path).toURI().toURL(); + URL[] urls = new URL[] {url}; + System.out.println(path); + System.out.println(url); + + // unload the custom class loader + boolean doUnload = false; + if (args[1].equals("true")) { + doUnload = true; + } else if (args[1].equals("false")) { + doUnload = false; + } else { + throw new RuntimeException("args[1] can only be either \"true\" or \"false\", actual " + args[1]); + } + + // should the CustomLoadee class be in the shared archive + boolean inArchive = false; + if (args[2].equals("true")) { + inArchive = true; + } else if (args[2].equals("false")) { + inArchive = false; + } else { + throw new RuntimeException("args[2] can only be either \"true\" or \"false\", actual " + args[1]); + } + + URLClassLoader urlClassLoader = + new URLClassLoader("HelloClassLoader", urls, null); + Class c = Class.forName(className, true, urlClassLoader); + System.out.println(c); + System.out.println(c.getClassLoader()); + Object o = c.newInstance(); + + // [1] Check that CustomLoadee is defined by the correct loader + if (c.getClassLoader() != urlClassLoader) { + throw new RuntimeException("c.getClassLoader() == " + c.getClassLoader() + + ", expected == " + urlClassLoader); + } + + // [2] Check that CustomLoadee is loaded from shared archive. + WhiteBox wb = WhiteBox.getWhiteBox(); + if(wb.isSharedClass(HelloUnload.class)) { + if (inArchive && !wb.isSharedClass(c)) { + throw new RuntimeException("wb.isSharedClass(c) should be true"); + } + } + + ClassUnloadCommon.failIf(!wb.isClassAlive(className), "should be live here"); + + if (doUnload) { + String loaderName = urlClassLoader.getName(); + int loadedRefcount = wb.getSymbolRefcount(loaderName); + System.out.println("Refcount of symbol " + loaderName + " is " + loadedRefcount); + + urlClassLoader = null; c = null; o = null; + ClassUnloadCommon.triggerUnloading(); + System.out.println("Is CustomLoadee alive? " + wb.isClassAlive(className)); + ClassUnloadCommon.failIf(wb.isClassAlive(className), "should have been unloaded"); + + int unloadedRefcount = wb.getSymbolRefcount(loaderName); + System.out.println("Refcount of symbol " + loaderName + " is " + unloadedRefcount); + + // refcount of a permanent symbol will not be decremented + if (loadedRefcount != 65535) { + ClassUnloadCommon.failIf(unloadedRefcount != (loadedRefcount - 1), "Refcount must be decremented"); + } + } + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/AppendClasspath.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/AppendClasspath.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary At run time, it is OK to append new elements to the classpath that was used at dump time. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds + * @modules java.base/jdk.internal.misc + * java.management + * jdk.jartool/sun.tools.jar + * @compile ../test-classes/Hello.java + * @compile ../test-classes/HelloMore.java + * @run driver AppendClasspath + */ + +import java.io.File; + +public class AppendClasspath extends DynamicArchiveTestBase { + + public static void main(String[] args) throws Exception { + runTest(AppendClasspath::testDefaultBase); + } + + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + doTest(topArchiveName); + } + + private static void doTest(String topArchiveName) throws Exception { + String appJar = JarBuilder.getOrCreateHelloJar(); + String appJar2 = JarBuilder.build("AppendClasspath_HelloMore", "HelloMore"); + + // Dump an archive with a specified JAR file in -classpath + dump(topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-cp", appJar, "Hello") + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + + // runtime with classpath containing the one used in dump time, + // i.e. the dump time classpath is a prefix of the runtime classpath. + run(topArchiveName, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar + File.pathSeparator + appJar2, + "HelloMore") + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldContain("Hello World ... More") + .shouldHaveExitValue(0); + }); + + // reverse the order of the 2 jar files so that the dump time classpath + // is no longer a prefix of the runtime classpath. The Hello class + // should be loaded from the jar file. + run(topArchiveName, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar2 + File.pathSeparator + appJar, + "HelloMore") + .assertAbnormalExit(output -> { + output.shouldContain("shared class paths mismatch") + .shouldHaveExitValue(1); + }); + + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/ArchiveConsistency.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/ArchiveConsistency.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Corrupt the header CRC fields of the top archive. VM should exit with an error. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/test-classes + * @build Hello sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar hello.jar Hello + * @run driver ClassFileInstaller sun.hotspot.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI ArchiveConsistency + */ + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.nio.file.StandardOpenOption; +import static java.nio.file.StandardOpenOption.READ; +import static java.nio.file.StandardOpenOption.WRITE; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import sun.hotspot.WhiteBox; + +public class ArchiveConsistency extends DynamicArchiveTestBase { + public static WhiteBox wb; + public static int int_size; // size of int + public static String[] shared_region_name = {"MiscCode", "ReadWrite", "ReadOnly", "MiscData"}; + public static int num_regions = shared_region_name.length; + + public static void main(String[] args) throws Exception { + runTest(ArchiveConsistency::testCustomBase); + } + + // Test with custom base archive + top archive + static void testCustomBase() throws Exception { + String topArchiveName = getNewArchiveName("top2"); + String baseArchiveName = getNewArchiveName("base"); + dumpBaseArchive(baseArchiveName); + doTest(baseArchiveName, topArchiveName); + } + + public static void setReadWritePermission(File file) throws Exception { + if (!file.canRead()) { + if (!file.setReadable(true)) { + throw new IOException("Cannot modify file " + file + " as readable"); + } + } + if (!file.canWrite()) { + if (!file.setWritable(true)) { + throw new IOException("Cannot modify file " + file + " as writable"); + } + } + } + + public static long readInt(FileChannel fc, long offset, int nbytes) throws Exception { + ByteBuffer bb = ByteBuffer.allocate(nbytes); + bb.order(ByteOrder.nativeOrder()); + fc.position(offset); + fc.read(bb); + return (nbytes > 4 ? bb.getLong(0) : bb.getInt(0)); + } + + public static long align_up_page(long l) throws Exception { + // wb is obtained in getFileOffsetInfo() which is called first in main() else we should call + // WhiteBox.getWhiteBox() here first. + int pageSize = wb.getVMPageSize(); + return (l + pageSize -1) & (~ (pageSize - 1)); + } + + public static void writeData(FileChannel fc, long offset, ByteBuffer bb) throws Exception { + fc.position(offset); + fc.write(bb); + fc.force(true); + } + + public static FileChannel getFileChannel(File jsa) throws Exception { + List arry = new ArrayList(); + arry.add(READ); + arry.add(WRITE); + return FileChannel.open(jsa.toPath(), new HashSet(arry)); + } + + public static void modifyJsaHeaderCRC(File jsa) throws Exception { + FileChannel fc = getFileChannel(jsa); + int_size = wb.getOffsetForName("int_size"); + System.out.println(" int_size " + int_size); + ByteBuffer bbuf = ByteBuffer.allocateDirect(int_size); + for (int i = 0; i < int_size; i++) { + bbuf.put((byte)0); + } + + int baseArchiveCRCOffset = wb.getOffsetForName("DynamicArchiveHeader::_base_archive_crc"); + int crc = 0; + System.out.printf("%-12s%-12s\n", "Space name", "CRC"); + for (int i = 0; i < 4; i++) { + baseArchiveCRCOffset += int_size * i; + System.out.println(" baseArchiveCRCOffset " + baseArchiveCRCOffset); + crc = (int)readInt(fc, baseArchiveCRCOffset, int_size ); + System.out.printf("%-11s%-12d\n", shared_region_name[i], crc); + bbuf.rewind(); + writeData(fc, baseArchiveCRCOffset, bbuf); + } + fc.force(true); + if (fc.isOpen()) { + fc.close(); + } + } + + private static void doTest(String baseArchiveName, String topArchiveName) throws Exception { + String appJar = ClassFileInstaller.getJarPath("hello.jar"); + String mainClass = "Hello"; + dump2(baseArchiveName, topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + + File jsa = new File(topArchiveName); + if (!jsa.exists()) { + throw new IOException(jsa + " does not exist!"); + } + + // Modify the CRC values in the header of the top archive. + wb = WhiteBox.getWhiteBox(); + setReadWritePermission(jsa); + modifyJsaHeaderCRC(jsa); + + run2(baseArchiveName, topArchiveName, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-XX:+VerifySharedSpaces", + "-cp", appJar, mainClass) + .assertAbnormalExit(output -> { + output.shouldContain("Header checksum verification failed") + .shouldContain("Unable to use shared archive") + .shouldHaveExitValue(1); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/ArrayKlasses.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/ArrayKlasses.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary handling of the existence of InstanceKlass::array_klasses() + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds + * /test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes + * @build ArrayKlassesApp + * @run driver ClassFileInstaller -jar ArrayKlasses.jar + * ArrayKlassesApp + * @run driver ArrayKlasses + */ + +public class ArrayKlasses extends DynamicArchiveTestBase { + public static void main(String[] args) throws Exception { + runTest(ArrayKlasses::test); + } + + static void test() throws Exception { + String topArchiveName = getNewArchiveName(); + String appJar = ClassFileInstaller.getJarPath("ArrayKlasses.jar"); + String mainClass = "ArrayKlassesApp"; + + dumpAndRun(topArchiveName, "-Xlog:cds+dynamic=debug", "-cp", appJar, mainClass); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/ClassResolutionFailure.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/ClassResolutionFailure.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Test with a jar file which contains only the main class but not the dependent class. + * The main class should be archived. During run time, the main class + * should be loaded from the archive. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes + * @build StrConcatApp + * @build MissingDependent + * @run driver ClassFileInstaller -jar missingDependent.jar MissingDependent + * @run driver ClassResolutionFailure + */ + +public class ClassResolutionFailure extends DynamicArchiveTestBase { + + public static void main(String[] args) throws Exception { + runTest(ClassResolutionFailure::testDefaultBase); + } + + // Test with default base archive + top archive + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + doTest(topArchiveName); + } + + private static void doTest(String topArchiveName) throws Exception { + String appJar = ClassFileInstaller.getJarPath("missingDependent.jar"); + String mainClass = "MissingDependent"; + + dump(topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-Xlog:class+load=trace", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + + run(topArchiveName, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("MissingDependent source: shared objects file") + .shouldHaveExitValue(0); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/DynamicArchiveTestBase.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/DynamicArchiveTestBase.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.io.File; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.cds.CDSOptions; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.cds.CDSTestUtils.Result; + +/** + * Base class for test cases in test/hotspot/jtreg/runtime/appcds/dynamicArchive/ + */ +class DynamicArchiveTestBase { + private static boolean executedIn_run = false; + + public static interface DynamicArchiveTest { + public void run() throws Exception; + } + + public static interface DynamicArchiveTestWithArgs { + public void run(String args[]) throws Exception; + } + + + /* + * Tests for dynamic archives should be written using this pattern: + * + * public class HelloDynamic extends DynamicArchiveTestBase { + * public static void main(String[] args) throws Exception { + * runTest(HelloDynamic::testDefaultBase); // launch one test case + * } + * + * // the body of a test case + * static void testDefaultBase() throws Exception { + * String topArchiveName = getNewArchiveName("top"); + * doTest(null, topArchiveName); + * } + * } + * + * The reason for this is so that we can clean up the archive files + * created by prior test cases. Otherwise tests with lots of + * test cases may fill up the scratch directory. + */ + public static void runTest(DynamicArchiveTest t) throws Exception { + executedIn_run = true; + try { + TestCommon.deletePriorArchives(); + t.run(); + } finally { + executedIn_run = false; + } + } + + public static void runTest(DynamicArchiveTestWithArgs t, String... args) throws Exception { + executedIn_run = true; + try { + TestCommon.deletePriorArchives(); + t.run(args); + } finally { + executedIn_run = false; + } + } + + public static String getNewArchiveName() { + return TestCommon.getNewArchiveName(); + } + public static String getNewArchiveName(String stem) { + return TestCommon.getNewArchiveName(stem); + } + + /** + * Execute a JVM using the base archive (given by baseArchiveName) with the command line + * (given by cmdLineSuffix). At JVM exit, dump all eligible classes into the top archive + * (give by topArchiveName). + * + * If baseArchiveName is null, use the JDK's default archive as the base archive. + */ + public static Result dump2(String baseArchiveName, String topArchiveName, String ... cmdLineSuffix) + throws Exception + { + String[] cmdLine = TestCommon.concat( + "-XX:ArchiveClassesAtExit=" + topArchiveName); + // to allow dynamic archive tests to be run in the "rt-non-cds-mode" + cmdLine = TestCommon.concat(cmdLine, "-Xshare:auto"); + if (baseArchiveName != null) { + cmdLine = TestCommon.concat(cmdLine, "-XX:SharedArchiveFile=" + baseArchiveName); + } + cmdLine = TestCommon.concat(cmdLine, cmdLineSuffix); + return execProcess("dump", cmdLine); + } + + public static Result dump2_WB(String baseArchiveName, String topArchiveName, String ... cmdLineSuffix) + throws Exception + { + return dump2(baseArchiveName, topArchiveName, + TestCommon.concat(wbRuntimeArgs(), cmdLineSuffix)); + } + + /** + * A convenience method similar to dump2, but always use the JDK's default archive + * as the base archive. + * + * Most dynamicArchive/*.java test cases should be using this method instead of run2. + */ + public static Result dump(String topArchiveName, String ... cmdLineSuffix) + throws Exception + { + return dump2(null, topArchiveName, cmdLineSuffix); + } + + /** + * Dump the base archive. The JDK's default class list is used (unless otherwise specified + * in cmdLineSuffix). + */ + public static void dumpBaseArchive(String baseArchiveName, String ... cmdLineSuffix) + throws Exception + { + CDSOptions opts = new CDSOptions(); + opts.setArchiveName(baseArchiveName); + opts.addSuffix(cmdLineSuffix); + opts.addSuffix("-Djava.class.path="); + OutputAnalyzer out = CDSTestUtils.createArchive(opts); + CDSTestUtils.checkDump(out); + } + + /** + * Same as dumpBaseArchive, but also add WhiteBox to the bootcp + */ + public static void dumpBaseArchive_WB(String baseArchiveName, String ... cmdLineSuffix) + throws Exception + { + dumpBaseArchive(baseArchiveName, + TestCommon.concat("-Xbootclasspath/a:" + getWhiteBoxJar(), cmdLineSuffix)); + } + + private static String getWhiteBoxJar() { + String wbJar = ClassFileInstaller.getJarPath("WhiteBox.jar"); + if (!(new File(wbJar)).exists()) { + throw new RuntimeException("Test error: your test must have " + + "'@run driver ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox'"); + } + return wbJar; + } + + private static String[] wbRuntimeArgs() { + return TestCommon.concat("-Xbootclasspath/a:" + getWhiteBoxJar(), + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI"); + } + + /** + * Execute a JVM using the base archive (given by baseArchiveName) and the top archive + * (give by topArchiveName), using the command line (given by cmdLineSuffix). + * + * If baseArchiveName is null, use the JDK's default archive as the base archive. + */ + public static Result run2(String baseArchiveName, String topArchiveName, String ... cmdLineSuffix) + throws Exception { + if (baseArchiveName == null && topArchiveName == null) { + throw new RuntimeException("Both baseArchiveName and topArchiveName cannot be null at the same time."); + } + String archiveFiles = (baseArchiveName == null) ? topArchiveName : + (topArchiveName == null) ? baseArchiveName : + baseArchiveName + File.pathSeparator + topArchiveName; + String[] cmdLine = TestCommon.concat( + "-Xshare:on", + "-XX:SharedArchiveFile=" + archiveFiles); + cmdLine = TestCommon.concat(cmdLine, cmdLineSuffix); + return execProcess("exec", cmdLine); + } + + public static Result run2_WB(String baseArchiveName, String topArchiveName, String ... cmdLineSuffix) + throws Exception + { + return run2(baseArchiveName, topArchiveName, + TestCommon.concat(wbRuntimeArgs(), cmdLineSuffix)); + } + + /** + * A convenience method similar to run2, but always use the JDK's default archive + * as the base archive. + * + * Most dynamicArchive/*.java test cases should be using this method instead of run2. + */ + public static Result run(String topArchiveName, String ... cmdLineSuffix) + throws Exception + { + return run2(null, topArchiveName, cmdLineSuffix); + } + + private static String getXshareMode(String[] cmdLine) { + for (int i = 0; i <= cmdLine.length - 1; i++) { + int j = cmdLine[i].indexOf("-Xshare:"); + if (j != -1) { + return (cmdLine[i].substring(j)); + } + } + return null; + } + + + private static Result execProcess(String mode, String[] cmdLine) throws Exception { + if (!executedIn_run) { + throw new Exception("Test error: dynamic archive tests must be executed via DynamicArchiveTestBase.run()"); + } + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); + OutputAnalyzer output = TestCommon.executeAndLog(pb, mode); + CDSOptions opts = new CDSOptions(); + String xShareMode = getXshareMode(cmdLine); + if (xShareMode != null) { + opts.setXShareMode(xShareMode); + } + return new Result(opts, output); + } + + /** + * A convenience method for dumping and running, using the default CDS archive from the + * JDK. Both dumping and running should exit normally. + */ + public static void dumpAndRun(String topArchiveName, String ... cmdLineSuffix) throws Exception { + dump(topArchiveName, cmdLineSuffix).assertNormalExit(); + run(topArchiveName, cmdLineSuffix).assertNormalExit(); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/DynamicFlag.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/DynamicFlag.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary The DynamicDumpShareSpaces flag is internal, setting it at the command line should have no effect. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds + * @modules java.base/jdk.internal.misc + * jdk.jartool/sun.tools.jar + * @compile ../test-classes/Hello.java + * @run driver DynamicFlag + */ + +public class DynamicFlag { + public static void main(String[] args) throws Exception { + TestCommon.test(JarBuilder.getOrCreateHelloJar(), + TestCommon.list("Hello"), "-XX:+DynamicDumpSharedSpaces", "Hello"); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/DynamicLotsOfClasses.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/DynamicLotsOfClasses.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.io.File; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.ArrayList; + +/* + * @test + * @summary Try to archive lots of classes by searching for classes from the jrt:/ file system. With JDK 12 + * this will produce an archive with over 30,000 classes. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes + * @build LoadClasses + * @build sun.hotspot.WhiteBox + * @modules jdk.jartool/sun.tools.jar + * @run driver ClassFileInstaller -jar loadclasses.jar LoadClasses + * @run driver ClassFileInstaller -jar whitebox.jar sun.hotspot.WhiteBox + * @run driver/timeout=500 DynamicLotsOfClasses + */ + +public class DynamicLotsOfClasses extends DynamicArchiveTestBase { + + public static void main(String[] args) throws Throwable { + runTest(DynamicLotsOfClasses::testDefaultBase); + } + + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + try { + doTest(topArchiveName); + } catch (Throwable th) { + System.out.println(th.toString()); + Exception ex = new Exception(th); + throw ex; + } + } + + private static void doTest(String topArchiveName) throws Throwable { + ArrayList list = new ArrayList<>(); + TestCommon.findAllClasses(list); + + String classList = System.getProperty("user.dir") + File.separator + + "LotsOfClasses.list"; + List lines = list; + Path file = Paths.get(classList); + Files.write(file, lines, Charset.forName("UTF-8")); + + String appJar = ClassFileInstaller.getJarPath("loadclasses.jar"); + String mainClass = "LoadClasses"; + + String whiteBoxJar = ClassFileInstaller.getJarPath("whitebox.jar"); + String bootClassPath = "-Xbootclasspath/a:" + whiteBoxJar; + dump(topArchiveName, + "--add-modules", + "ALL-SYSTEM", + "-Xlog:hashtables", + "-Xmx500m", + "-Xlog:cds,cds+dynamic", + bootClassPath, + "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", + "-cp", appJar, mainClass, classList) + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/ExcludedClasses.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/ExcludedClasses.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Unreferenced app classes during dump time should not be included in the archive. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds + * /test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes + * @build ExcludedClassesApp + * @run driver ClassFileInstaller -jar ExcludedClasses.jar + * ExcludedClassesApp + * ExcludedClassesApp$NotLinkedSuper + * ExcludedClassesApp$NotLinkedChild + * ExcludedClassesApp$NotLinkedInterface + * @run driver ExcludedClasses + */ + +public class ExcludedClasses extends DynamicArchiveTestBase { + public static void main(String[] args) throws Exception { + runTest(ExcludedClasses::test); + } + + static void test() throws Exception { + String topArchiveName = getNewArchiveName(); + String appJar = ClassFileInstaller.getJarPath("ExcludedClasses.jar"); + String mainClass = "ExcludedClassesApp"; + + dumpAndRun(topArchiveName, "-Xlog:cds+dynamic=debug", "-cp", appJar, mainClass); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/HelloDynamic.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/HelloDynamic.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Hello World test for dynamic archive + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/test-classes + * @build Hello + * @run driver ClassFileInstaller -jar hello.jar Hello + * @run driver HelloDynamic + */ + +public class HelloDynamic extends DynamicArchiveTestBase { + public static void main(String[] args) throws Exception { + runTest(HelloDynamic::testDefaultBase); + runTest(HelloDynamic::testCustomBase); + } + + // (1) Test with default base archive + top archive + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + doTest(null, topArchiveName); + } + + // (2) Test with custom base archive + top archive + static void testCustomBase() throws Exception { + String topArchiveName = getNewArchiveName("top2"); + String baseArchiveName = getNewArchiveName("base"); + dumpBaseArchive(baseArchiveName); + doTest(baseArchiveName, topArchiveName); + } + + private static void doTest(String baseArchiveName, String topArchiveName) throws Exception { + String appJar = ClassFileInstaller.getJarPath("hello.jar"); + String mainClass = "Hello"; + dump2(baseArchiveName, topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + run2(baseArchiveName, topArchiveName, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Hello source: shared objects file") + .shouldHaveExitValue(0); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/HelloDynamicCustom.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/HelloDynamicCustom.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Hello World test for dynamic archive with custom loader + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/customLoader/test-classes /runtime/testlibrary + * @build HelloUnload CustomLoadee ClassUnloadCommon + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar hello.jar HelloUnload ClassUnloadCommon ClassUnloadCommon$1 ClassUnloadCommon$TestFailure + * @run driver ClassFileInstaller -jar hello_custom.jar CustomLoadee + * @run driver ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox + * @run driver HelloDynamicCustom + */ + +import java.io.File; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class HelloDynamicCustom { + private static final String ARCHIVE_NAME = + System.getProperty("test.classes") + File.separator + "HelloDynamicCustom.jsa"; + + public static void main(String[] args) throws Exception { + String wbJar = ClassFileInstaller.getJarPath("WhiteBox.jar"); + String use_whitebox_jar = "-Xbootclasspath/a:" + wbJar; + String appJar = ClassFileInstaller.getJarPath("hello.jar"); + String customJarPath = ClassFileInstaller.getJarPath("hello_custom.jar"); + String mainAppClass = "HelloUnload"; + + ProcessBuilder dumpPb = ProcessTools.createJavaProcessBuilder(true, + use_whitebox_jar, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-Xshare:auto", + "-XX:ArchiveClassesAtExit=" + ARCHIVE_NAME, + "-cp", appJar, + mainAppClass, customJarPath, "false", "false"); + TestCommon.executeAndLog(dumpPb, "dump") + .shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x") + .shouldNotContain("klasses.*=.*CustomLoadee") // Fixme -- use a better way to decide if a class has been archived + .shouldHaveExitValue(0); + + ProcessBuilder execPb = ProcessTools.createJavaProcessBuilder(true, + use_whitebox_jar, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-Xlog:class+load", + "-Xlog:cds=debug", + "-Xlog:cds+dynamic=info", + "-Xshare:auto", + "-XX:SharedArchiveFile=" + ARCHIVE_NAME, + "-cp", appJar, + mainAppClass, customJarPath, "false", "true"); + TestCommon.executeAndLog(execPb, "exec") + .shouldContain("HelloUnload source: shared objects file") + .shouldContain("CustomLoadee source: shared objects file") + .shouldHaveExitValue(0); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/HelloDynamicCustomUnload.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/HelloDynamicCustomUnload.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Hello World test for dynamic archive with custom loader. + * Attempt will be made to unload the custom loader during + * dump time and run time. The custom loader will be unloaded + * during dump time. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/customLoader/test-classes /runtime/testlibrary + * @build HelloUnload CustomLoadee ClassUnloadCommon + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar hello.jar HelloUnload ClassUnloadCommon ClassUnloadCommon$1 ClassUnloadCommon$TestFailure + * @run driver ClassFileInstaller -jar hello_custom.jar CustomLoadee + * @run driver ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox + * @run driver HelloDynamicCustomUnload + */ + +import java.io.File; + +public class HelloDynamicCustomUnload extends DynamicArchiveTestBase { + private static final String ARCHIVE_NAME = + System.getProperty("test.classes") + File.separator + "HelloDynamicCustomUnload.jsa"; + + public static void main(String[] args) throws Exception { + runTest(HelloDynamicCustomUnload::testDefaultBase); + } + + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("HelloDynamicCustomUnload-top"); + doTest(topArchiveName); + } + + private static void doTest(String topArchiveName) throws Exception { + String wbJar = ClassFileInstaller.getJarPath("WhiteBox.jar"); + String use_whitebox_jar = "-Xbootclasspath/a:" + wbJar; + String appJar = ClassFileInstaller.getJarPath("hello.jar"); + String customJarPath = ClassFileInstaller.getJarPath("hello_custom.jar"); + String mainAppClass = "HelloUnload"; + + dump(topArchiveName, + use_whitebox_jar, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-Xmn8m", + "-Xlog:cds,cds+dynamic=debug,class+unload=debug", + "-XX:ArchiveClassesAtExit=" + ARCHIVE_NAME, + "-cp", appJar, + mainAppClass, customJarPath, "true", "false") + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x") + .shouldNotContain("klasses.*=.*CustomLoadee") // Fixme -- use a better way to decide if a class has been archived + .shouldHaveExitValue(0); + }); + + run(topArchiveName, + use_whitebox_jar, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-Xlog:class+load,cds=debug,class+unload=debug", + "-XX:SharedArchiveFile=" + ARCHIVE_NAME, + "-cp", appJar, + mainAppClass, customJarPath, "true", "false") + .assertNormalExit(output -> { + output.shouldContain("HelloUnload source: shared objects file") + .shouldMatch(".class.load. CustomLoadee source:.*hello_custom.jar") + .shouldHaveExitValue(0); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/JITInteraction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/JITInteraction.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Test interaction with JIT threads during vm exit. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes + * @build TestJIT + * @modules jdk.jartool/sun.tools.jar + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar testjit.jar TestJIT + * @run driver JITInteraction + */ + +public class JITInteraction extends DynamicArchiveTestBase { + + public static void main(String[] args) throws Exception { + runTest(JITInteraction::testDefaultBase); + } + + // Test with default base archive + top archive + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + doTest(topArchiveName); + } + + private static void doTest(String topArchiveName) throws Exception { + String appJar = ClassFileInstaller.getJarPath("testjit.jar"); + String mainClass = "TestJIT"; + + dump2_WB(null, topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic", + "-XX:-UseOnStackReplacement", + "-XX:+PrintCompilation", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/MainModuleOnly.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/MainModuleOnly.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/** + * @test + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds + * @modules jdk.compiler + * jdk.jartool/sun.tools.jar + * jdk.jlink + * @run driver MainModuleOnly + * @summary Test some scenarios with a main modular jar specified in the --module-path and -cp options in the command line. + */ + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.Platform; + +public class MainModuleOnly extends DynamicArchiveTestBase { + + private static final Path USER_DIR = Paths.get(System.getProperty("user.dir")); + + private static final String FS = File.separator; + private static final String TEST_SRC = System.getProperty("test.src") + + FS + ".." + FS + "jigsaw" + FS + "modulepath"; + + private static final Path SRC_DIR = Paths.get(TEST_SRC, "src"); + private static final Path MODS_DIR = Paths.get("mods"); + + // the module name of the test module + private static final String TEST_MODULE1 = "com.simple"; + + // the module main class + private static final String MAIN_CLASS = "com.simple.Main"; + + private static Path moduleDir = null; + private static Path moduleDir2 = null; + private static Path destJar = null; + + public static void buildTestModule() throws Exception { + + // javac -d mods/$TESTMODULE --module-path MOD_DIR src/$TESTMODULE/** + JarBuilder.compileModule(SRC_DIR.resolve(TEST_MODULE1), + MODS_DIR.resolve(TEST_MODULE1), + MODS_DIR.toString()); + + + moduleDir = Files.createTempDirectory(USER_DIR, "mlib"); + moduleDir2 = Files.createTempDirectory(USER_DIR, "mlib2"); + + Path srcJar = moduleDir.resolve(TEST_MODULE1 + ".jar"); + destJar = moduleDir2.resolve(TEST_MODULE1 + ".jar"); + String classes = MODS_DIR.resolve(TEST_MODULE1).toString(); + JarBuilder.createModularJar(srcJar.toString(), classes, MAIN_CLASS); + Files.copy(srcJar, destJar); + + } + + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + doTest(topArchiveName); + } + + public static void main(String... args) throws Exception { + runTest(MainModuleOnly::testDefaultBase); + } + + public static void doTest(String topArchiveName) throws Exception { + // compile the modules and create the modular jar files + buildTestModule(); + // create an archive with both -cp and --module-path in the command line. + // Only the class in the modular jar in the --module-path will be archived; + // the class in the modular jar in the -cp won't be archived. + dump2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", destJar.toString(), + "--module-path", moduleDir.toString(), + "-m", TEST_MODULE1) + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + + // run with the archive using the same command line as in dump time. + // The main class should be loaded from the archive. + run2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug,class+load=trace", + "-cp", destJar.toString(), + "--module-path", moduleDir.toString(), + "-m", TEST_MODULE1) + .assertNormalExit(output -> { + output.shouldContain("[class,load] com.simple.Main source: shared objects file") + .shouldHaveExitValue(0); + }); + + // run with the archive with the main class name inserted before the -m. + // The main class name will be picked up before the module name. So the + // main class should be loaded from the jar in the -cp. + run2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug,class+load=trace", + "-cp", destJar.toString(), + "--module-path", moduleDir.toString(), + MAIN_CLASS, "-m", TEST_MODULE1) + .assertNormalExit(out -> + out.shouldMatch(".class.load. com.simple.Main source:.*com.simple.jar")); + + // run with the archive with exploded module. Since during dump time, we + // only archive classes from the modular jar in the --module-path, the + // main class should be loaded from the exploded module directory. + run2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug,class+load=trace", + "-cp", destJar.toString(), + "--module-path", MODS_DIR.toString(), + "-m", TEST_MODULE1 + "/" + MAIN_CLASS) + .assertNormalExit(out -> { + out.shouldMatch(".class.load. com.simple.Main source:.*com.simple") + .shouldContain(MODS_DIR.toString()); + }); + + // run with the archive with the --upgrade-module-path option. + // CDS will be disabled with this options and the main class will be + // loaded from the modular jar. + run2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug,class+load=trace", + "-cp", destJar.toString(), + "--upgrade-module-path", moduleDir.toString(), + "--module-path", moduleDir.toString(), + "-m", TEST_MODULE1) + .assertSilentlyDisabledCDS(out -> { + out.shouldHaveExitValue(0) + .shouldMatch("CDS is disabled when the.*option is specified") + .shouldMatch(".class.load. com.simple.Main source:.*com.simple.jar"); + }); + // run with the archive with the --limit-modules option. + // CDS will be disabled with this options and the main class will be + // loaded from the modular jar. + run2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug,class+load=trace", + "-cp", destJar.toString(), + "--limit-modules", "java.base," + TEST_MODULE1, + "--module-path", moduleDir.toString(), + "-m", TEST_MODULE1) + .assertSilentlyDisabledCDS(out -> { + out.shouldHaveExitValue(0) + .shouldMatch("CDS is disabled when the.*option is specified") + .shouldMatch(".class.load. com.simple.Main source:.*com.simple.jar"); + }); + // run with the archive with the --patch-module option. + // CDS will be disabled with this options and the main class will be + // loaded from the modular jar. + run2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug,class+load=trace", + "-cp", destJar.toString(), + "--patch-module", TEST_MODULE1 + "=" + MODS_DIR.toString(), + "--module-path", moduleDir.toString(), + "-m", TEST_MODULE1) + .assertSilentlyDisabledCDS(out -> { + out.shouldHaveExitValue(0) + .shouldMatch("CDS is disabled when the.*option is specified") + .shouldMatch(".class.load. com.simple.Main source:.*com.simple.jar"); + }); + // modify the timestamp of the jar file + (new File(destJar.toString())).setLastModified(System.currentTimeMillis() + 2000); + // run with the archive and the jar with modified timestamp. + // It should fail due to timestamp of the jar doesn't match the one + // used during dump time. + run2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug,class+load=trace", + "-cp", destJar.toString(), + "--module-path", moduleDir.toString(), + "-m", TEST_MODULE1) + .assertAbnormalExit( + "A jar file is not the one used while building the shared archive file:"); + // create an archive with a non-empty directory in the --module-path. + // The dumping process will exit with an error due to non-empty directory + // in the --module-path. + dump2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", destJar.toString(), + "--module-path", MODS_DIR.toString(), + "-m", TEST_MODULE1 + "/" + MAIN_CLASS) + .assertAbnormalExit(output -> { + output.shouldMatch("Error: non-empty directory.*com.simple"); + }); + + // test module path with very long length + // + // This test can't be run on the windows platform due to an existing + // issue in ClassLoader::get_canonical_path() (JDK-8190737). + if (Platform.isWindows()) { + System.out.println("Long module path test cannot be tested on the Windows platform."); + return; + } + Path longDir = USER_DIR; + int pathLen = longDir.toString().length(); + int PATH_LEN = 2034; + int MAX_DIR_LEN = 250; + while (pathLen < PATH_LEN) { + int remaining = PATH_LEN - pathLen; + int subPathLen = remaining > MAX_DIR_LEN ? MAX_DIR_LEN : remaining; + char[] chars = new char[subPathLen]; + Arrays.fill(chars, 'x'); + String subPath = new String(chars); + longDir = Paths.get(longDir.toString(), subPath); + pathLen = longDir.toString().length(); + } + File longDirFile = new File(longDir.toString()); + try { + longDirFile.mkdirs(); + } catch (Exception e) { + throw e; + } + Path longDirJar = longDir.resolve(TEST_MODULE1 + ".jar"); + // IOException results from the Files.copy() call on platform + // such as MacOS X. Test can't be proceeded further with the + // exception. + try { + Files.copy(destJar, longDirJar); + } catch (java.io.IOException ioe) { + System.out.println("Caught IOException from Files.copy(). Cannot continue."); + return; + } + dump2(null, topArchiveName, + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", destJar.toString(), + "-Xlog:exceptions=trace", + "--module-path", longDirJar.toString(), + "-m", TEST_MODULE1) + .ifAbnormalExit(output -> { + output.shouldMatch("os::stat error.*CDS dump aborted"); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/MethodSorting.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/MethodSorting.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary When HelloA and HelloB are copied into the dynamic archive, the Symbols + * for their method's names will have a different sorting order. This requires + * that the dumped InstanceKlass to re-sort their "methods" array and re-layout the vtables/itables. + * A regression test for an earlier bug in DynamicArchiveBuilder::relocate_buffer_to_target(). + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/test-classes + * /test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes + * @build MethodSortingApp + * @run driver ClassFileInstaller -jar method_sorting.jar + * MethodSortingApp + * MethodSortingApp$HelloA + * MethodSortingApp$HelloA1 + * MethodSortingApp$HelloB + * MethodSortingApp$HelloB1 + * MethodSortingApp$InterfaceA + * MethodSortingApp$InterfaceB + * MethodSortingApp$ImplementorA + * MethodSortingApp$ImplementorA1 + * MethodSortingApp$ImplementorB + * MethodSortingApp$ImplementorB1 + * @run driver MethodSorting + */ + +public class MethodSorting extends DynamicArchiveTestBase { + public static void main(String[] args) throws Exception { + runTest(MethodSorting::test); + } + + static void test() throws Exception { + String topArchiveName = getNewArchiveName(); + String appJar = ClassFileInstaller.getJarPath("method_sorting.jar"); + String mainClass = "MethodSortingApp"; + + dumpAndRun(topArchiveName, "-Xlog:cds+dynamic=debug", "-cp", appJar, mainClass); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/MissingArchive.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/MissingArchive.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.io.File; + +/* + * @test + * @summary error handling when either (or both) of the base/top archives are missing. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/test-classes + * @build GenericTestApp sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar GenericTestApp.jar GenericTestApp + * @run driver MissingArchive + */ + +public class MissingArchive extends DynamicArchiveTestBase { + private static final String TOP = "top"; + private static final String BASE = "base"; + private static final String BOTH = "base/top"; + private static final String NONE = "none"; + + public static void main(String[] args) throws Exception { + runTest(MissingArchive::test, TOP); + runTest(MissingArchive::test, BASE); + runTest(MissingArchive::test, BOTH); + runTest(MissingArchive::test, NONE); + } + + static void delete(String fileName) { + File f = new File(fileName); + f.delete(); + } + + static void test(String args[]) throws Exception { + String topArchiveName = getNewArchiveName("top"); + String baseArchiveName = getNewArchiveName("base"); + dumpBaseArchive(baseArchiveName); + + String appJar = ClassFileInstaller.getJarPath("GenericTestApp.jar"); + String mainClass = "GenericTestApp"; + dump2_WB(baseArchiveName, topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + + // Use -Xshare:auto so top archive can fail after base archive has succeeded, + // but the app will continue to run. + String[] cmdline = TestCommon.concat( + "-Xlog:cds*", + "-Xshare:auto", + "-cp", appJar, mainClass); + + + String mode = args[0]; + + if (mode.contains(BASE)) { + delete(baseArchiveName); + cmdline = TestCommon.concat(cmdline, "assertNotShared:java.lang.Object"); + } else { + cmdline = TestCommon.concat(cmdline, "assertShared:java.lang.Object"); + } + + if (mode.contains(TOP)) { + delete(topArchiveName); + } + + if (mode.equals(NONE)) { + cmdline = TestCommon.concat(cmdline, "assertShared:GenericTestApp"); + } else { + cmdline = TestCommon.concat(cmdline, "assertNotShared:GenericTestApp"); + } + + run2_WB(baseArchiveName, topArchiveName, cmdline).assertNormalExit(); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/NoClassToArchive.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/NoClassToArchive.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary A few edge cases where there's no class to be included in the dynamic archive. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes + * @build StrConcatApp + * @run driver ClassFileInstaller -jar strConcatApp.jar StrConcatApp + * @run driver NoClassToArchive + */ + +import java.io.File; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class NoClassToArchive extends DynamicArchiveTestBase { + static final String warningMessage = + "There is no class to be included in the dynamic archive"; + static final String classList = System.getProperty("test.classes") + + File.separator + "NoClassToArchive.list"; + static final String appClass = "StrConcatApp"; + + public static void main(String[] args) throws Exception { + runTest(NoClassToArchive::testDefaultBase); + runTest(NoClassToArchive::testCustomBase); + } + + // (1) Test with default base archive + top archive + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + doTest(null, topArchiveName); + } + + // (2) Test with custom base archive + top archive + static void testCustomBase() throws Exception { + String topArchiveName = getNewArchiveName("top2"); + String baseArchiveName = getNewArchiveName("base"); + doTestCustomBase(baseArchiveName, topArchiveName); + } + + private static void doTest(String baseArchiveName, String topArchiveName) throws Exception { + dump2(baseArchiveName, topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-Xlog:class+load=trace", + "-version") + .assertNormalExit(output -> { + if (output.getStdout().contains("jrt:/")) { + System.out.println("test skipped: this platform uses non-archived classes when running -version"); + } else { + output.shouldContain(warningMessage); + } + }); + + dump2(baseArchiveName, topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-Xlog:class+load=trace", + "-help") + .assertNormalExit(output -> { + // some classes will be loaded from the java.base module + output.shouldContain("java.text.MessageFormat source: jrt:/java.base"); + }); + } + + private static void doTestCustomBase(String baseArchiveName, String topArchiveName) throws Exception { + String appJar = ClassFileInstaller.getJarPath("strConcatApp.jar"); + // dump class list by running the StrConcatApp + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + true, + "-XX:DumpLoadedClassList=" + classList, + "-cp", + appJar, + appClass); + OutputAnalyzer output = TestCommon.executeAndLog(pb, "dumpClassList"); + TestCommon.checkExecReturn(output, 0, true, "length = 0"); + + // create a custom base archive based on the class list + dumpBaseArchive(baseArchiveName, "-XX:SharedClassListFile=" + classList); + + // create a dynamic archive with the custom base archive + // no class should be included in the dynamic archive + dump2(baseArchiveName, topArchiveName, "-version") + .assertNormalExit(out -> { + out.shouldMatch(warningMessage); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/SharedArchiveFileOption.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/SharedArchiveFileOption.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Some negative tests for the SharedArchiveFile option + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/test-classes + * @build Hello + * @run driver ClassFileInstaller -jar hello.jar Hello + * @run driver SharedArchiveFileOption + */ + +import java.io.File; + +public class SharedArchiveFileOption extends DynamicArchiveTestBase { + public static void main(String[] args) throws Exception { + runTest(SharedArchiveFileOption::testCustomBase); + } + + static String baseArchiveName2; + static void testCustomBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + String baseArchiveName = getNewArchiveName("base"); + baseArchiveName2 = getNewArchiveName("base2"); + dumpBaseArchive(baseArchiveName); + dumpBaseArchive(baseArchiveName2); + doTest(baseArchiveName, topArchiveName); + } + + private static void doTest(String baseArchiveName, String topArchiveName) throws Exception { + String appJar = ClassFileInstaller.getJarPath("hello.jar"); + String mainClass = "Hello"; + String dummyArchiveName = getNewArchiveName("dummy"); + + // -Xshare:dump specified with -XX:ArchiveClassesAtExit + dump2(dummyArchiveName, dummyArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-Xshare:dump", + "-cp", appJar, mainClass) + .assertAbnormalExit(output -> { + output.shouldContain("-XX:ArchiveClassesAtExit cannot be used with -Xshare:dump"); + }); + + // more than 1 archive file specified in -XX:SharedArchiveFile during + // dynamic dumpgin + String dummyArchives = dummyArchiveName + File.pathSeparator + dummyArchiveName; + dump2(dummyArchives, dummyArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-cp", appJar, mainClass) + .assertAbnormalExit(output -> { + output.shouldContain("Cannot have more than 1 archive file specified in -XX:SharedArchiveFile during CDS dumping"); + }); + + // normal dynamic archive dumping + dump2(baseArchiveName, topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + + // same archive file specified for -XX:SharedArchiveFile and -XX:ArchiveClassesAtExit + dump2(baseArchiveName, baseArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-cp", appJar, mainClass) + .assertAbnormalExit(output -> { + output.shouldContain("Cannot have the same archive file specified for -XX:SharedArchiveFile and -XX:ArchiveClassesAtExit: " + + baseArchiveName); + }); + + + // a top archive specified in the base archive position + run2(topArchiveName, baseArchiveName, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar, mainClass) + .assertAbnormalExit(output -> { + output.shouldMatch("Not a base shared archive:.*top.*.jsa"); + }); + + // a base archive specified in the top archive position + run2(baseArchiveName, baseArchiveName2, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar, mainClass) + .assertAbnormalExit(output -> { + output.shouldMatch("Not a top shared archive:.*base.*.jsa"); + }); + + // more than 2 archives specified in the -XX:ShareArchiveFile option + String baseArchives = baseArchiveName + File.pathSeparator + baseArchiveName2; + run2(baseArchives, topArchiveName, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar, mainClass) + .assertAbnormalExit(output -> { + output.shouldContain( + "Cannot have more than 2 archive files specified in the -XX:SharedArchiveFile option"); + }); + + // base archive not specified + final String topArchive = File.pathSeparator + topArchiveName; + run2(topArchive, null, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar, mainClass) + .assertAbnormalExit(output -> { + output.shouldContain( + "Base archive was not specified: " + topArchive); + }); + + // top archive not specified + final String baseArchive = baseArchiveName + File.pathSeparator; + run2(baseArchive, null, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar, mainClass) + .assertAbnormalExit(output -> { + output.shouldContain( + "Top archive was not specified: " + baseArchive); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/UnsupportedBaseArchive.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/UnsupportedBaseArchive.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/* + * @test + * @summary unsupported base archive tests + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/test-classes + * @modules jdk.jartool/sun.tools.jar + * @compile ../test-classes/Hello.java + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox + * @run driver UnsupportedBaseArchive + */ + +public class UnsupportedBaseArchive extends DynamicArchiveTestBase { + private static final Path USER_DIR = Paths.get(System.getProperty("user.dir")); + + private static final String FS = File.separator; + private static final String TEST_SRC = System.getProperty("test.src") + + FS + ".." + FS + "jigsaw" + FS + "modulepath"; + + private static final Path SRC_DIR = Paths.get(TEST_SRC, "src"); + private static final Path MODS_DIR = Paths.get("mods"); + + // the module name of the test module + private static final String TEST_MODULE = "com.simple"; + + // the module main class + private static final String MAIN_CLASS = "com.simple.Main"; + + private static Path moduleDir = null; + private static Path srcJar = null; + + private static final String warningBCP = + "Dynamic archiving is disabled because base layer archive has appended boot classpath"; + + private static final String warningModulePath = + "Dynamic archiving is disabled because base layer archive has module path"; + + public static void buildTestModule() throws Exception { + + // javac -d mods/$TESTMODULE --module-path MOD_DIR src/$TESTMODULE/** + JarBuilder.compileModule(SRC_DIR.resolve(TEST_MODULE), + MODS_DIR.resolve(TEST_MODULE), + MODS_DIR.toString()); + + + moduleDir = Files.createTempDirectory(USER_DIR, "mlib"); + srcJar = moduleDir.resolve(TEST_MODULE + ".jar"); + String classes = MODS_DIR.resolve(TEST_MODULE).toString(); + JarBuilder.createModularJar(srcJar.toString(), classes, MAIN_CLASS); + } + + public static void main(String[] args) throws Exception { + runTest(UnsupportedBaseArchive::test); + } + + static void test(String args[]) throws Exception { + String topArchiveName = getNewArchiveName("top"); + String baseArchiveName = getNewArchiveName("base"); + + // create a base archive with -Xbootclasspath/a:whitebox.jar + dumpBaseArchive_WB(baseArchiveName); + + String appJar = JarBuilder.getOrCreateHelloJar(); + String mainClass = "Hello"; + + // dumping of dynamic archive should be disabled with a warning message + // if the base archive contains -Xbootclasspath/a entries. + dump2_WB(baseArchiveName, topArchiveName, + "-Xlog:cds*", + "-Xlog:cds+dynamic=debug", + "-Xlog:class+path=info", + "-cp", appJar, mainClass) + .assertNormalExit(warningBCP); + + // create a base archive with the --module-path option + buildTestModule(); + baseArchiveName = getNewArchiveName("base-with-module"); + dumpBaseArchive(baseArchiveName, + "-cp", srcJar.toString(), + "--module-path", moduleDir.toString(), + "-m", TEST_MODULE); + + // dumping of dynamic archive should be disabled with a warning message + // if the base archive contains --module-path entries. + topArchiveName = getNewArchiveName("top-with-module"); + dump2(baseArchiveName, topArchiveName, + "-Xlog:cds*", + "-Xlog:cds+dynamic=debug", + "-Xlog:class+path=info", + "-cp", srcJar.toString(), + "--module-path", moduleDir.toString(), + "-m", TEST_MODULE) + .assertNormalExit(warningModulePath); + + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/UnusedCPDuringDump.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/UnusedCPDuringDump.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary non-empty dir in -cp should be fine during dump time if only classes + * from the system modules are being loaded even though some are + * defined to the PlatformClassLoader and AppClassLoader. + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds + * @modules jdk.jartool/sun.tools.jar + * @compile ../test-classes/Hello.java + * @run main/othervm -Dtest.cds.copy.child.stdout=false UnusedCPDuringDump + */ + +import java.io.File; + +public class UnusedCPDuringDump extends DynamicArchiveTestBase { + + public static void main(String[] args) throws Exception { + runTest(UnusedCPDuringDump::testDefaultBase); + } + + static void testDefaultBase() throws Exception { + String topArchiveName = getNewArchiveName("top"); + doTest(topArchiveName); + } + + private static void doTest(String topArchiveName) throws Exception { + File dir = new File(System.getProperty("user.dir")); + File emptydir = new File(dir, "emptydir"); + emptydir.mkdir(); + String appJar = JarBuilder.getOrCreateHelloJar(); + String bootClassPath = "-Xbootclasspath/a:" + appJar; + + // Dumping with a non-empty directory in the -cp. + // It should be fine because the app class won't be loaded from the + // -cp, it is being loaded from the bootclasspath. + dump(topArchiveName, + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + bootClassPath, + "-cp", dir.getPath(), + "Hello") + .assertNormalExit(output -> { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + }); + + // Running with -cp different from dumping. It should be fine because + // the runtime classpath won't be checked against unused classpath + // during dump time. + run(topArchiveName, + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + bootClassPath, + "-cp", appJar, "Hello") + .assertNormalExit(output -> { + output.shouldContain("Hello World") + .shouldHaveExitValue(0); + }); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/WrongTopClasspath.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/WrongTopClasspath.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.io.File; + +/* + * @test + * @summary correct classpath for bottom archive, but bad classpath for top archive + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/appcds /test/hotspot/jtreg/runtime/appcds/test-classes + * @build GenericTestApp sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar WhiteBox.jar sun.hotspot.WhiteBox + * @run driver ClassFileInstaller -jar GenericTestApp.jar GenericTestApp + * @run driver ClassFileInstaller -jar WrongJar.jar GenericTestApp + * @run driver WrongTopClasspath + */ + +public class WrongTopClasspath extends DynamicArchiveTestBase { + + public static void main(String[] args) throws Exception { + runTest(WrongTopClasspath::test); + } + + static void test(String args[]) throws Exception { + String topArchiveName = getNewArchiveName("top"); + String baseArchiveName = getNewArchiveName("base"); + dumpBaseArchive(baseArchiveName); + + String appJar = ClassFileInstaller.getJarPath("GenericTestApp.jar"); + String wrongJar = ClassFileInstaller.getJarPath("WrongJar.jar"); + String mainClass = "GenericTestApp"; + + // Dump the top archive using "-cp GenericTestApp.jar" ... + dump2_WB(baseArchiveName, topArchiveName, + "-Xlog:cds*", + "-Xlog:cds+dynamic=debug", + "-cp", appJar, mainClass) + .assertNormalExit(); + + // ... but try to load the top archive using "-cp WrongJar.jar". + // Use -Xshare:auto so top archive can fail after base archive has succeeded, + // but the app will continue to run. + run2_WB(baseArchiveName, topArchiveName, + "-Xlog:cds*", + "-Xlog:cds+dynamic=debug", + "-Xlog:class+path=info", + "-Xshare:auto", + "-cp", wrongJar, mainClass, + "assertShared:java.lang.Object", // base archive still useable + "assertNotShared:GenericTestApp") // but top archive is not useable + .assertNormalExit("The top archive failed to load"); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/ArrayKlassesApp.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/ArrayKlassesApp.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +public class ArrayKlassesApp { + public static void main(String args[]) { + ArrayKlassesApp[][] array = new ArrayKlassesApp[1][2]; + for (int i=0; i<1; i++) { + for (int j=0; j<2; j++) { + array[i][j] = new ArrayKlassesApp(); + } + } + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/ExcludedClassesApp.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/ExcludedClassesApp.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +public class ExcludedClassesApp { + interface NotLinkedInterface {} + static class NotLinkedSuper { + + } + + static class NotLinkedChild extends NotLinkedSuper implements NotLinkedInterface { + + } + + public static NotLinkedSuper notUsedMethod() { + return new NotLinkedChild(); + } + + public static void main(String args[]) { + + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/LoadClasses.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/LoadClasses.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.io.File; +import java.util.List; +import java.util.Scanner; +import sun.hotspot.WhiteBox; + +public class LoadClasses { + + public static void main (String[] args) throws Throwable { + String classList = args[0]; + Scanner sc = new Scanner(new File(classList)); + WhiteBox wb = WhiteBox.getWhiteBox(); + int count = 0; + while (sc.hasNextLine()) { + String cn = sc.nextLine().replace('/', '.'); + try { + Class cls = Class.forName(cn, false, LoadClasses.class.getClassLoader()); + wb.linkClass(cls); + count++; + } catch (Throwable ex) { + System.out.println("Loading failed: " + cn); + System.out.println(ex.toString()); + } + } + System.out.println("Loaded classes = " + count); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/MethodSortingApp.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/MethodSortingApp.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.SynchronousQueue; + +public class MethodSortingApp { + static class HelloA { + String aaaa() { return "aaaa"; } + String bbbb() { return "bbbb"; } + String dddd() { return "dddd"; } + String eeee() { return "eeee"; } + String gggg() { return "gggg"; } + } + + static class HelloA1 extends HelloA { + String aaaa() { return "aaa-"; } + String dddd() { return "ddd-"; } + String gggg() { return "ggg-"; } + } + + static class HelloB { + String aaaa() { return "aaaa"; } + String cccc() { return "cccc"; } + String dddd() { return "dddd"; } + String ffff() { return "ffff"; } + String gggg() { return "gggg"; } + } + + static class HelloB1 extends HelloB { + String aaaa() { return "aaa-"; } + String dddd() { return "ddd-"; } + String gggg() { return "ggg-"; } + } + + // Default methods in interfaces must be sorted + static interface InterfaceA { + default public String aaa() { return "aaa";} + default public String bbb() { return "bbb";} + default public String ddd() { return "ddd";} + default public String eee() { return "eee";} + default public String ggg() { return "ggg";} + } + + static class ImplementorA implements InterfaceA { + public String aaa() { return "aa-";} + } + + static class ImplementorA1 extends ImplementorA { + public String bbb() { return "bb-";} + } + + static interface InterfaceB { + default public String aaa() { return "aaa"; } + default public String ccc() { return "ccc"; } + default public String ddd() { return "ddd"; } + default public String fff() { return "fff"; } + default public String ggg() { return "ggg"; } + } + + static class ImplementorB implements InterfaceB { + public String ggg() { return "gg-";} + } + + static class ImplementorB1 extends ImplementorB { + public String fff() { return "ff-";} + } + + + public static void main(String args[]) { + testSimpleMethods(); + testDefaultMethods(); + testMixedInterfaces(); + } + + static void testSimpleMethods() { + // When HelloA and HelloB are copied into the dynamic archive, the Symbols + // for their method's names will have a different sorting order. This requires + // that the dumped InstanceKlass to re-sort their "methods" array and re-layout + // the vtables/itables. + HelloA1 a1 = new HelloA1(); + HelloA a = new HelloA(); + assertEqual(a.aaaa(), "aaaa"); + assertEqual(a.bbbb(), "bbbb"); + assertEqual(a.dddd(), "dddd"); + assertEqual(a.eeee(), "eeee"); + assertEqual(a.gggg(), "gggg"); + + assertEqual(a1.aaaa(), "aaa-"); + assertEqual(a1.bbbb(), "bbbb"); + assertEqual(a1.dddd(), "ddd-"); + assertEqual(a1.eeee(), "eeee"); + assertEqual(a1.gggg(), "ggg-"); + + HelloB b = new HelloB(); + assertEqual(b.aaaa(), "aaaa"); + assertEqual(b.cccc(), "cccc"); + assertEqual(b.dddd(), "dddd"); + assertEqual(b.ffff(), "ffff"); + assertEqual(b.gggg(), "gggg"); + + HelloB b1 = new HelloB1(); + assertEqual(b1.aaaa(), "aaa-"); + assertEqual(b1.cccc(), "cccc"); + assertEqual(b1.dddd(), "ddd-"); + assertEqual(b1.ffff(), "ffff"); + assertEqual(b1.gggg(), "ggg-"); + } + + static void testDefaultMethods() { + InterfaceA a1 = new ImplementorA1(); + InterfaceA a = new ImplementorA(); + + assertEqual(a.aaa(), "aa-"); + assertEqual(a.bbb(), "bbb"); + assertEqual(a.ddd(), "ddd"); + assertEqual(a.eee(), "eee"); + assertEqual(a.ggg(), "ggg"); + + assertEqual(a1.aaa(), "aa-"); + assertEqual(a1.bbb(), "bb-"); + assertEqual(a1.ddd(), "ddd"); + assertEqual(a1.eee(), "eee"); + assertEqual(a1.ggg(), "ggg"); + + InterfaceB b = new ImplementorB(); + InterfaceB b1 = new ImplementorB1(); + + assertEqual(b.aaa(), "aaa"); + assertEqual(b.ccc(), "ccc"); + assertEqual(b.ddd(), "ddd"); + assertEqual(b.fff(), "fff"); + assertEqual(b.ggg(), "gg-"); + + assertEqual(b1.aaa(), "aaa"); + assertEqual(b1.ccc(), "ccc"); + assertEqual(b1.ddd(), "ddd"); + assertEqual(b1.fff(), "ff-"); + assertEqual(b1.ggg(), "gg-"); + } + + // This is a regression test for an earlier bug in + // DynamicArchiveBuilder::relocate_buffer_to_target() + static void testMixedInterfaces() { + Object xx = new SynchronousQueue(); + BlockingQueue yy = (BlockingQueue)xx; + } + + static private void assertEqual(String a, String b) { + if (!a.equals(b)) { + throw new RuntimeException(a + " is not equal to " + b); + } else { + System.out.println("Expected: " + a + ", got: " + b); + } + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/MissingDependent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/MissingDependent.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +public class MissingDependent { + public static void main(String args[]) throws Exception { + try { + Object obj = new StrConcatApp(); + } catch (Throwable e) { + String cause = e.getCause().toString(); + if (cause.equals("java.lang.ClassNotFoundException: StrConcatApp")) { + e.printStackTrace(); + } else { + throw e; + } + } + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/StrConcatApp.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/StrConcatApp.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +public class StrConcatApp { + public static void main(String args[]) { + System.out.println("length = " + args.length); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/TestJIT.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/dynamicArchive/test-classes/TestJIT.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import sun.hotspot.WhiteBox; + +import java.lang.reflect.Method; +import java.util.HashMap; + +public class TestJIT { + + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + static void doSomething() { + HashMap map = new HashMap<>(); + for (int i=0; i<400; i++) { + // Call these methods so that the class/field/method references used + // by these methods are resolved. This allows C2 to compile more code. + String x = "Hello" + i; + map.put(x, x); + map.get(x); + } + } + + static public void main(String[] args) throws NoSuchMethodException { + Method put_method = HashMap.class.getDeclaredMethod("put", Object.class, Object.class); + Method get_method = HashMap.class.getDeclaredMethod("get", Object.class); + Method test_method = TestJIT.class.getDeclaredMethod("doSomething"); + + doSomething(); + + // 4 == CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION => C2 + WHITE_BOX.enqueueMethodForCompilation(get_method, 4); + WHITE_BOX.enqueueMethodForCompilation(put_method, 4); + WHITE_BOX.enqueueMethodForCompilation(test_method, 4); + + // Try to start dynamic dumping while the above compilations are still in progesss + System.exit(0); + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/jigsaw/PatchModule/MismatchedPatchModule.java --- a/test/hotspot/jtreg/runtime/appcds/jigsaw/PatchModule/MismatchedPatchModule.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/jigsaw/PatchModule/MismatchedPatchModule.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2019, 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 @@ -74,11 +74,12 @@ // Case 2: --patch-module specified for run time but not for dump time System.out.println("Case 2: --patch-module specified for run time but not for dump time"); + String appJar = JarBuilder.build("PatchMain-app", "PatchMain"); output = - TestCommon.dump(null, + TestCommon.dump(appJar, TestCommon.list("javax/naming/spi/NamingManager"), "PatchMain", "javax.naming.spi.NamingManager"); - TestCommon.checkDump(output, "Loading classes to share"); + TestCommon.checkDump(output); // javax.naming.spi.NamingManager is patched at runtime TestCommon.run( diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/jigsaw/PatchModule/PatchJavaBase.java --- a/test/hotspot/jtreg/runtime/appcds/jigsaw/PatchModule/PatchJavaBase.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/jigsaw/PatchModule/PatchJavaBase.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2019, 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 @@ -58,18 +58,25 @@ moduleJar = TestCommon.getTestJar("javabase.jar"); System.out.println("Test dumping with --patch-module"); + String runError = "Unable to use shared archive: CDS is disabled when java.base module is patched"; + String dumpingError = "Cannot use the following option when dumping the shared archive: --patch-module"; + String errMsg; + if (TestCommon.isDynamicArchive()) { + errMsg = runError; + } else { + errMsg = dumpingError; + } OutputAnalyzer output = TestCommon.dump(null, null, "--patch-module=java.base=" + moduleJar, "PatchMain", "java.lang.NewClass"); output.shouldHaveExitValue(1) - .shouldContain("Cannot use the following option when dumping the shared archive: --patch-module"); + .shouldContain(errMsg); TestCommon.run( "-XX:+UnlockDiagnosticVMOptions", "--patch-module=java.base=" + moduleJar, "PatchMain", "java.lang.NewClass") - .assertAbnormalExit("Unable to use shared archive", - "CDS is disabled when java.base module is patched"); + .assertAbnormalExit(runError); } } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/jigsaw/PatchModule/PatchMain.java --- a/test/hotspot/jtreg/runtime/appcds/jigsaw/PatchModule/PatchMain.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/jigsaw/PatchModule/PatchMain.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2019, 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 @@ -27,6 +27,9 @@ public class PatchMain { public static void main(String[] args) throws Exception { for (int i = 0; i < args.length; i++) { + if (args[i].equals("cdsutils.DynamicDumpHelper")) { + break; + } Class.forName(args[i]); } } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/AddModules.java --- a/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/AddModules.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/AddModules.java Fri May 17 08:29:55 2019 -0700 @@ -29,6 +29,7 @@ * @modules jdk.compiler * jdk.jartool/sun.tools.jar * jdk.jlink + * @compile ../../test-classes/Hello.java * @run driver AddModules * @summary sanity test the --add-modules option */ @@ -63,6 +64,7 @@ private static Path subJar = null; private static Path mainJar1 = null; private static Path mainJar2 = null; + private static String appJar; public static void buildTestModule() throws Exception { @@ -96,18 +98,19 @@ } public static void main(String... args) throws Exception { + appJar = JarBuilder.getOrCreateHelloJar(); // compile the modules and create the modular jar files buildTestModule(); String appClasses[] = {MAIN_CLASS1, MAIN_CLASS2, APP_CLASS}; // create an archive with the classes in the modules built in the // previous step OutputAnalyzer output = TestCommon.createArchive( - null, appClasses, + appJar, appClasses, "--module-path", moduleDir.toString(), "--add-modules", MAIN_MODULE1 + "," + MAIN_MODULE2); TestCommon.checkDump(output); - String prefix[] = {"-Djava.class.path=", "-Xlog:class+load=trace"}; + String prefix[] = {"-Djava.class.path=" + appJar, "-Xlog:class+load=trace"}; // run the com.greetings module with the archive with the --module-path // the same as the one during dump time. diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/JvmtiAddPath.java --- a/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/JvmtiAddPath.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/JvmtiAddPath.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, 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 @@ -121,7 +121,9 @@ "-Xlog:class+load", "JvmtiApp", "noadd", MAIN_CLASS); // appcds should be enabled System.out.println("Test case 2: add to boot classpath only - should find Hello.class in boot loader"); - run(check_appcds_disabled, appJar, + String[] toCheck = TestCommon.isDynamicArchive() ? check_appcds_enabled : + check_appcds_disabled; + run(toCheck, appJar, "-Xlog:class+load=trace", modulePath, "JvmtiApp", "bootonly", addbootJar, MAIN_CLASS); // appcds should be disabled diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/MainModuleOnly.java --- a/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/MainModuleOnly.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/jigsaw/modulepath/MainModuleOnly.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, 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 @@ -29,7 +29,7 @@ * @modules jdk.compiler * jdk.jartool/sun.tools.jar * jdk.jlink - * @run driver MainModuleOnly + * @run main/othervm MainModuleOnly * @summary Test some scenarios with a main modular jar specified in the --module-path and -cp options in the command line. */ @@ -39,6 +39,7 @@ import java.nio.file.Paths; import java.util.Arrays; +import jdk.test.lib.cds.CDSTestUtils.Result; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.Platform; @@ -61,6 +62,8 @@ private static Path moduleDir2 = null; private static Path destJar = null; + private static final String jarFileError = "A jar file is not the one used while building the shared archive file:"; + public static void buildTestModule() throws Exception { // javac -d mods/$TESTMODULE --module-path MOD_DIR src/$TESTMODULE/** @@ -167,17 +170,21 @@ // run with the archive and the jar with modified timestamp. // It should fail due to timestamp of the jar doesn't match the one // used during dump time. - TestCommon.run("-cp", destJar.toString(), + Result res = TestCommon.run("-cp", destJar.toString(), + "-Xlog:cds", "--module-path", moduleDir.toString(), - "-m", TEST_MODULE1) - .assertAbnormalExit( - "A jar file is not the one used while building the shared archive file:"); + "-m", TEST_MODULE1); + res.assertAbnormalExit(jarFileError); // create an archive with a non-empty directory in the --module-path. // The dumping process will exit with an error due to non-empty directory // in the --module-path. + String mainModule = TEST_MODULE1; + if (TestCommon.isDynamicArchive()) { + mainModule += "/" + MAIN_CLASS; + } output = TestCommon.createArchive(destJar.toString(), appClasses, "--module-path", MODS_DIR.toString(), - "-m", TEST_MODULE1); + "-m", mainModule); output.shouldHaveExitValue(1) .shouldMatch("Error: non-empty directory.*com.simple"); diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/jigsaw/overridetests/OverrideTests.java --- a/test/hotspot/jtreg/runtime/appcds/jigsaw/overridetests/OverrideTests.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/jigsaw/overridetests/OverrideTests.java Fri May 17 08:29:55 2019 -0700 @@ -26,8 +26,10 @@ * @test * @requires vm.cds * @modules java.base/jdk.internal.misc + * jdk.jartool/sun.tools.jar * @library ../.. * @library /test/lib + * @compile ../../test-classes/Hello.java * @run driver OverrideTests * @summary AppCDS tests for overriding archived classes with -p and --upgrade-module-path */ @@ -65,7 +67,7 @@ private static final String TEST_SRC = System.getProperty("test.src"); private static final Path SRC_DIR = Paths.get(TEST_SRC, "src"); private static final Path MODS_DIR = Paths.get("mods"); - + private static String appJar; // the module that is upgraded private static final String[] UPGRADED_MODULES = {"jdk.compiler", "java.net.http"}; private static final Path[] UPGRADEDMODS_DIR = {Paths.get("upgradedmod1"), Paths.get("upgradedmod2")}; @@ -82,6 +84,7 @@ public static void main(String[] args) throws Exception { + appJar = JarBuilder.getOrCreateHelloJar(); OverrideTests tests = new OverrideTests(); tests.compileModulesAndDumpArchive(); tests.testAppClassOverriding(); @@ -111,7 +114,7 @@ Asserts.assertTrue(compiled, TEST_MODULE + " did not compile"); // dump the archive with jdk.compiler and java.net.http classes in the class list - OutputAnalyzer output = TestCommon.dump(null /* appJar*/, TestCommon.list(ARCHIVE_CLASSES)); + OutputAnalyzer output = TestCommon.dump(appJar, TestCommon.list(ARCHIVE_CLASSES)); TestCommon.checkDump(output); // Make sure all the classes where successfully archived. for (String archiveClass : ARCHIVE_CLASSES) { @@ -160,7 +163,7 @@ int upgradeModIdx = isAppLoader ? 0 : 1; String expectedException = "java.lang.module.FindException: Unable to compute the hash"; String prefix[] = new String[3]; - prefix[0] = "-Djava.class.path="; + prefix[0] = "-Djava.class.path=" + appJar; prefix[1] = "--add-modules"; prefix[2] = "java.net.http"; diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/jvmti/dumpingWithAgent/DumpingWithJavaAgent.java --- a/test/hotspot/jtreg/runtime/appcds/jvmti/dumpingWithAgent/DumpingWithJavaAgent.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/jvmti/dumpingWithAgent/DumpingWithJavaAgent.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, 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 @@ -105,7 +105,7 @@ if (!TestCommon.isUnableToMap(output)) { output.shouldHaveExitValue(0); output.shouldContain(errorMessage); - output.shouldMatch(".class.load. Hello source:.*DumpingWithJavaAgent.jar"); + output.shouldMatch(".class.load.* Hello source:.*DumpingWithJavaAgent.jar"); // CDS dumping with a java agent without the AllowArchvingWithJavaAgent diagnostic option. // VM will exit with an error message. @@ -116,4 +116,3 @@ .shouldHaveExitValue(1); } } - diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/jvmti/transformRelatedClasses/TransformRelatedClassesAppCDS.java --- a/test/hotspot/jtreg/runtime/appcds/jvmti/transformRelatedClasses/TransformRelatedClassesAppCDS.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/jvmti/transformRelatedClasses/TransformRelatedClassesAppCDS.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2019, 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 @@ -154,6 +154,12 @@ return; } + if (TestCommon.isDynamicArchive()) { + log("custom loader class list not applicable to dynamic archive" + + " - skipping test case for custom loader"); + return; + } + String appClasses[] = { "CustomLoaderApp", }; diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/test-classes/DummyClassHelper.java --- a/test/hotspot/jtreg/runtime/appcds/test-classes/DummyClassHelper.java Fri May 17 10:48:02 2019 -0400 +++ b/test/hotspot/jtreg/runtime/appcds/test-classes/DummyClassHelper.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019, 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 @@ -24,6 +24,7 @@ import java.lang.*; import java.lang.reflect.*; +import jdk.test.lib.cds.CDSTestUtils; import sun.hotspot.WhiteBox; public class DummyClassHelper { @@ -50,9 +51,14 @@ cls = Class.forName(classNames[i]); checkDummyMethod(cls, classNames[i]); if (doWBCheck) { - if (!wb.isSharedClass(cls)) { - throw new java.lang.RuntimeException(classNames[i] + - ".class should be in shared space."); + // FIXME: for dynamic archive, the class loaded from the + // bootclasspath jar during dump time is not loaded from the + // archive during run time. + if (!CDSTestUtils.isDynamicArchive()) { + if (!wb.isSharedClass(cls)) { + throw new java.lang.RuntimeException(classNames[i] + + ".class should be in shared space."); + } } } } diff -r d4e7ccaf1445 -r 1512d88b24c6 test/hotspot/jtreg/runtime/appcds/test-classes/GenericTestApp.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/jtreg/runtime/appcds/test-classes/GenericTestApp.java Fri May 17 08:29:55 2019 -0700 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import sun.hotspot.WhiteBox; + +/** + * This is a generic test app for testing if classes are loaded from the CDS archive + * or not (without having to parse -Xlog:class+load, or writing your own WhiteBox apps). + * Usage: + * [1] Create an archive with WhiteBox enabled. + * [2] Run this app with arguments such as + * "assertShared:java.lang.Object" + * "assertNotShared:NotSharedClassName" + * + * We can probably add other kinds of simple tests as well .... + * + * FIXME: enhance WB API to check if a particular archive has failed. So you can say + * assertShared:0,java.lang.Object + * to assert that java.lang.Object is shared from archive #0 (i.e., base archive). + */ +public class GenericTestApp { + private static final WhiteBox wb = WhiteBox.getWhiteBox(); + + public static void main(String args[]) throws Exception { + System.out.println("GenericTestApp started. WhiteBox = " + wb); + System.out.println("cdsMemoryMappingFailed() = " + cdsMemoryMappingFailed()); + + for (String s : args) { + Class c; + if ((c = getClass(s, "assertShared:")) != null) { + assertShared(c); + } + else if ((c = getClass(s, "assertNotShared:")) != null) { + assertNotShared(c); + } + else { + throw new RuntimeException("Unknown option: " + s); + } + System.out.println("passed: " + s); + } + } + + private static Class getClass(String s, String prefix) throws Exception { + if (s.startsWith(prefix)) { + return Class.forName(s.substring(prefix.length())); + } else { + return null; + } + } + + private static boolean cdsMemoryMappingFailed() { + return wb.cdsMemoryMappingFailed(); + } + + private static void assertShared(Class klass) { + if (!cdsMemoryMappingFailed()) { + if (!wb.isSharedClass(klass)) { + throw new RuntimeException("Class should be shared but is not: " + klass); + } + } else { + // FIXME -- need to throw jtreg.SkippedException + System.out.println("Cannot test for wb.isSharedClass(" + klass + ") because CDS mapping has failed"); + } + } + + private static void assertNotShared(Class klass) { + if (wb.isSharedClass(klass)) { + throw new RuntimeException("Class should be shared but is not: " + klass); + } + } +} diff -r d4e7ccaf1445 -r 1512d88b24c6 test/lib/jdk/test/lib/cds/CDSTestUtils.java --- a/test/lib/jdk/test/lib/cds/CDSTestUtils.java Fri May 17 10:48:02 2019 -0400 +++ b/test/lib/jdk/test/lib/cds/CDSTestUtils.java Fri May 17 08:29:55 2019 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019, 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 @@ -43,6 +43,8 @@ public static final String MSG_COMPRESSION_MUST_BE_USED = "Unable to use shared archive: UseCompressedOops and UseCompressedClassPointers must be on for UseSharedSpaces."; + public static final boolean DYNAMIC_DUMP = Boolean.getBoolean("test.dynamic.cds.archive"); + public interface Checker { public void check(OutputAnalyzer output) throws Exception; } @@ -246,7 +248,7 @@ cmd.add("-Xlog:cds,cds+hashtables"); if (opts.archiveName == null) opts.archiveName = getDefaultArchiveName(); - cmd.add("-XX:SharedArchiveFile=./" + opts.archiveName); + cmd.add("-XX:SharedArchiveFile=" + opts.archiveName); if (opts.classList != null) { File classListFile = makeClassList(opts.classList); @@ -260,12 +262,20 @@ return executeAndLog(pb, "dump"); } + public static boolean isDynamicArchive() { + return DYNAMIC_DUMP; + } // check result of 'dump-the-archive' operation, that is "-Xshare:dump" public static OutputAnalyzer checkDump(OutputAnalyzer output, String... extraMatches) throws Exception { - output.shouldContain("Loading classes to share"); + if (!DYNAMIC_DUMP) { + output.shouldContain("Loading classes to share"); + } else { + output.shouldContain("Buffer-space to target-space delta") + .shouldContain("Written dynamic archive 0x"); + } output.shouldHaveExitValue(0); for (String match : extraMatches) { diff -r d4e7ccaf1445 -r 1512d88b24c6 test/lib/sun/hotspot/WhiteBox.java --- a/test/lib/sun/hotspot/WhiteBox.java Fri May 17 10:48:02 2019 -0400 +++ b/test/lib/sun/hotspot/WhiteBox.java Fri May 17 08:29:55 2019 -0700 @@ -514,6 +514,7 @@ // Sharing & archiving public native String getDefaultArchivePath(); + public native boolean cdsMemoryMappingFailed(); public native boolean isSharingEnabled(); public native boolean isShared(Object o); public native boolean isSharedClass(Class c); @@ -522,6 +523,7 @@ public native boolean isJFRIncludedInVmBuild(); public native boolean isJavaHeapArchiveSupported(); public native Object getResolvedReferences(Class c); + public native void linkClass(Class c); public native boolean areOpenArchiveHeapObjectsMapped(); // Compiler Directive