8159262: Walking PackageEntry Export and ModuleEntry Reads Must Occur Only When Neccessary And Wait Until ClassLoader's Aliveness Determined
Summary: Fixed an issue in class unloading to delay walk until class loader's aliveness is determined of modularity lists to remove dead modules
Reviewed-by: coleenp, dholmes, sspitsyn, zgu
--- a/hotspot/src/share/vm/classfile/classLoaderData.cpp Tue Jun 28 10:37:52 2016 +0200
+++ b/hotspot/src/share/vm/classfile/classLoaderData.cpp Tue Jun 28 10:11:01 2016 -0400
@@ -501,13 +501,26 @@
}
}
-/**
- * Returns true if this class loader data is for the platform class loader.
- */
+// Returns true if this class loader data is for the system class loader.
+bool ClassLoaderData::is_system_class_loader_data() const {
+ return SystemDictionary::is_system_class_loader(class_loader());
+}
+
+// Returns true if this class loader data is for the platform class loader.
bool ClassLoaderData::is_platform_class_loader_data() const {
return SystemDictionary::is_platform_class_loader(class_loader());
}
+// Returns true if this class loader data is one of the 3 builtin
+// (boot, application/system or platform) class loaders. Note, the
+// builtin loaders are not freed by a GC.
+bool ClassLoaderData::is_builtin_class_loader_data() const {
+ Handle classLoaderHandle = class_loader();
+ return (is_the_null_class_loader_data() ||
+ SystemDictionary::is_system_class_loader(classLoaderHandle) ||
+ SystemDictionary::is_platform_class_loader(classLoaderHandle));
+}
+
Metaspace* ClassLoaderData::metaspace_non_null() {
assert(!DumpSharedSpaces, "wrong metaspace!");
// If the metaspace has not been allocated, create a new one. Might want
@@ -957,12 +970,6 @@
data = _head;
while (data != NULL) {
if (data->is_alive(is_alive_closure)) {
- if (data->packages_defined()) {
- data->packages()->purge_all_package_exports();
- }
- if (data->modules_defined()) {
- data->modules()->purge_all_module_reads();
- }
// clean metaspace
if (walk_all_metadata) {
data->classes_do(InstanceKlass::purge_previous_versions);
@@ -990,6 +997,23 @@
}
if (seen_dead_loader) {
+ // Walk a ModuleEntry's reads and a PackageEntry's exports lists
+ // to determine if there are modules on those lists that are now
+ // dead and should be removed. A module's life cycle is equivalent
+ // to its defining class loader's life cycle. Since a module is
+ // considered dead if its class loader is dead, these walks must
+ // occur after each class loader's aliveness is determined.
+ data = _head;
+ while (data != NULL) {
+ if (data->packages_defined()) {
+ data->packages()->purge_all_package_exports();
+ }
+ if (data->modules_defined()) {
+ data->modules()->purge_all_module_reads();
+ }
+ data = data->next();
+ }
+
post_class_unload_events();
}
--- a/hotspot/src/share/vm/classfile/classLoaderData.hpp Tue Jun 28 10:37:52 2016 +0200
+++ b/hotspot/src/share/vm/classfile/classLoaderData.hpp Tue Jun 28 10:11:01 2016 -0400
@@ -270,7 +270,9 @@
bool is_the_null_class_loader_data() const {
return this == _the_null_class_loader_data;
}
+ bool is_system_class_loader_data() const;
bool is_platform_class_loader_data() const;
+ bool is_builtin_class_loader_data() const;
// The Metaspace is created lazily so may be NULL. This
// method will allocate a Metaspace if needed.
--- a/hotspot/src/share/vm/classfile/moduleEntry.cpp Tue Jun 28 10:37:52 2016 +0200
+++ b/hotspot/src/share/vm/classfile/moduleEntry.cpp Tue Jun 28 10:11:01 2016 -0400
@@ -40,7 +40,6 @@
ModuleEntry* ModuleEntryTable::_javabase_module = NULL;
-
void ModuleEntry::set_location(Symbol* location) {
if (_location != NULL) {
// _location symbol's refcounts are managed by ModuleEntry,
@@ -115,10 +114,35 @@
// Lazily create a module's reads list
_reads = new (ResourceObj::C_HEAP, mtModule)GrowableArray<ModuleEntry*>(MODULE_READS_SIZE, true);
}
+
+ // Determine, based on this newly established read edge to module m,
+ // if this module's read list should be walked at a GC safepoint.
+ set_read_walk_required(m->loader_data());
+
+ // Establish readability to module m
_reads->append_if_missing(m);
}
}
+// If the module's loader, that a read edge is being established to, is
+// not the same loader as this module's and is not one of the 3 builtin
+// class loaders, then this module's reads list must be walked at GC
+// safepoint. Modules have the same life cycle as their defining class
+// loaders and should be removed if dead.
+void ModuleEntry::set_read_walk_required(ClassLoaderData* m_loader_data) {
+ assert_locked_or_safepoint(Module_lock);
+ if (!_must_walk_reads &&
+ loader_data() != m_loader_data &&
+ !m_loader_data->is_builtin_class_loader_data()) {
+ _must_walk_reads = true;
+ if (log_is_enabled(Trace, modules)) {
+ ResourceMark rm;
+ log_trace(modules)("ModuleEntry::set_read_walk_required(): module %s reads list must be walked",
+ (name() != NULL) ? name()->as_C_string() : UNNAMED_MODULE);
+ }
+ }
+}
+
bool ModuleEntry::has_reads() const {
assert_locked_or_safepoint(Module_lock);
return ((_reads != NULL) && !_reads->is_empty());
@@ -127,14 +151,28 @@
// Purge dead module entries out of reads list.
void ModuleEntry::purge_reads() {
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
- if (has_reads()) {
+
+ if (_must_walk_reads && has_reads()) {
+ // This module's _must_walk_reads flag will be reset based
+ // on the remaining live modules on the reads list.
+ _must_walk_reads = false;
+
+ if (log_is_enabled(Trace, modules)) {
+ ResourceMark rm;
+ log_trace(modules)("ModuleEntry::purge_reads(): module %s reads list being walked",
+ (name() != NULL) ? name()->as_C_string() : UNNAMED_MODULE);
+ }
+
// Go backwards because this removes entries that are dead.
int len = _reads->length();
for (int idx = len - 1; idx >= 0; idx--) {
ModuleEntry* module_idx = _reads->at(idx);
- ClassLoaderData* cld = module_idx->loader();
- if (cld->is_unloading()) {
+ ClassLoaderData* cld_idx = module_idx->loader_data();
+ if (cld_idx->is_unloading()) {
_reads->delete_at(idx);
+ } else {
+ // Update the need to walk this module's reads based on live modules
+ set_read_walk_required(cld_idx);
}
}
}
@@ -248,7 +286,7 @@
entry->set_module(loader_data->add_handle(module_handle));
}
- entry->set_loader(loader_data);
+ entry->set_loader_data(loader_data);
entry->set_version(version);
entry->set_location(location);
@@ -379,7 +417,7 @@
p2i(this),
name() == NULL ? UNNAMED_MODULE : name()->as_C_string(),
p2i(module()),
- loader()->loader_name(),
+ loader_data()->loader_name(),
version() != NULL ? version()->as_C_string() : "NULL",
location() != NULL ? location()->as_C_string() : "NULL",
BOOL_TO_STR(!can_read_all_unnamed()), p2i(next()));
@@ -401,5 +439,5 @@
}
void ModuleEntry::verify() {
- guarantee(loader() != NULL, "A module entry must be associated with a loader.");
+ guarantee(loader_data() != NULL, "A module entry must be associated with a loader.");
}
--- a/hotspot/src/share/vm/classfile/moduleEntry.hpp Tue Jun 28 10:37:52 2016 +0200
+++ b/hotspot/src/share/vm/classfile/moduleEntry.hpp Tue Jun 28 10:11:01 2016 -0400
@@ -43,6 +43,7 @@
// It contains:
// - Symbol* containing the module's name.
// - pointer to the java.lang.reflect.Module for this module.
+// - pointer to the java.security.ProtectionDomain shared by classes defined to this module.
// - ClassLoaderData*, class loader of this module.
// - a growable array containg other module entries that this module can read.
// - a flag indicating if this module can read all unnamed modules.
@@ -54,56 +55,58 @@
jobject _module; // java.lang.reflect.Module
jobject _pd; // java.security.ProtectionDomain, cached
// for shared classes from this module
- ClassLoaderData* _loader;
+ ClassLoaderData* _loader_data;
GrowableArray<ModuleEntry*>* _reads; // list of modules that are readable by this module
Symbol* _version; // module version number
Symbol* _location; // module location
bool _can_read_all_unnamed;
bool _has_default_read_edges; // JVMTI redefine/retransform support
+ bool _must_walk_reads; // walk module's reads list at GC safepoints to purge out dead modules
TRACE_DEFINE_TRACE_ID_FIELD;
enum {MODULE_READS_SIZE = 101}; // Initial size of list of modules that the module can read.
public:
void init() {
_module = NULL;
- _loader = NULL;
+ _loader_data = NULL;
_pd = NULL;
_reads = NULL;
_version = NULL;
_location = NULL;
_can_read_all_unnamed = false;
_has_default_read_edges = false;
+ _must_walk_reads = false;
}
- Symbol* name() const { return literal(); }
- void set_name(Symbol* n) { set_literal(n); }
+ Symbol* name() const { return literal(); }
+ void set_name(Symbol* n) { set_literal(n); }
- jobject module() const { return _module; }
- void set_module(jobject j) { _module = j; }
+ jobject module() const { return _module; }
+ void set_module(jobject j) { _module = j; }
// The shared ProtectionDomain reference is set once the VM loads a shared class
// originated from the current Module. The referenced ProtectionDomain object is
// created by the ClassLoader when loading a class (shared or non-shared) from the
// Module for the first time. This ProtectionDomain object is used for all
// classes from the Module loaded by the same ClassLoader.
- Handle shared_protection_domain();
- void set_shared_protection_domain(ClassLoaderData *loader_data,
- Handle pd);
+ Handle shared_protection_domain();
+ void set_shared_protection_domain(ClassLoaderData *loader_data, Handle pd);
- ClassLoaderData* loader() const { return _loader; }
- void set_loader(ClassLoaderData* l) { _loader = l; }
+ ClassLoaderData* loader_data() const { return _loader_data; }
+ void set_loader_data(ClassLoaderData* l) { _loader_data = l; }
+
+ Symbol* version() const { return _version; }
+ void set_version(Symbol* version);
- Symbol* version() const { return _version; }
- void set_version(Symbol* version);
-
- Symbol* location() const { return _location; }
- void set_location(Symbol* location);
+ Symbol* location() const { return _location; }
+ void set_location(Symbol* location);
- bool can_read(ModuleEntry* m) const;
- bool has_reads() const;
- void add_read(ModuleEntry* m);
+ bool can_read(ModuleEntry* m) const;
+ bool has_reads() const;
+ void add_read(ModuleEntry* m);
+ void set_read_walk_required(ClassLoaderData* m_loader_data);
- bool is_named() const { return (literal() != NULL); }
+ bool is_named() const { return (name() != NULL); }
bool can_read_all_unnamed() const {
assert(is_named() || _can_read_all_unnamed == true,
@@ -178,7 +181,7 @@
ModuleEntry* _unnamed_module;
ModuleEntry* new_entry(unsigned int hash, Handle module_handle, Symbol* name, Symbol* version,
- Symbol* location, ClassLoaderData* class_loader);
+ Symbol* location, ClassLoaderData* loader_data);
void add_entry(int index, ModuleEntry* new_entry);
int entry_size() const { return BasicHashtable<mtModule>::entry_size(); }
--- a/hotspot/src/share/vm/classfile/modules.cpp Tue Jun 28 10:37:52 2016 +0200
+++ b/hotspot/src/share/vm/classfile/modules.cpp Tue Jun 28 10:11:01 2016 -0400
@@ -113,7 +113,7 @@
const char *package_name = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(package));
if (package_name == NULL) return NULL;
TempNewSymbol pkg_symbol = SymbolTable::new_symbol(package_name, CHECK_NULL);
- PackageEntryTable* package_entry_table = module_entry->loader()->packages();
+ PackageEntryTable* package_entry_table = module_entry->loader_data()->packages();
assert(package_entry_table != NULL, "Unexpected null package entry table");
return package_entry_table->lookup_only(pkg_symbol);
}
@@ -868,7 +868,7 @@
package_name, module_entry->name()->as_C_string());
TempNewSymbol pkg_symbol = SymbolTable::new_symbol(package_name, CHECK);
- PackageEntryTable* package_table = module_entry->loader()->packages();
+ PackageEntryTable* package_table = module_entry->loader_data()->packages();
assert(package_table != NULL, "Missing package_table");
bool pkg_exists = false;
--- a/hotspot/src/share/vm/classfile/packageEntry.cpp Tue Jun 28 10:37:52 2016 +0200
+++ b/hotspot/src/share/vm/classfile/packageEntry.cpp Tue Jun 28 10:11:01 2016 -0400
@@ -25,6 +25,7 @@
#include "precompiled.hpp"
#include "classfile/moduleEntry.hpp"
#include "classfile/packageEntry.hpp"
+#include "logging/log.hpp"
#include "memory/resourceArea.hpp"
#include "oops/symbol.hpp"
#include "runtime/handles.inline.hpp"
@@ -53,12 +54,40 @@
if (!has_qual_exports_list()) {
// Lazily create a package's qualified exports list.
// Initial size is small, do not anticipate export lists to be large.
- _qualified_exports =
- new (ResourceObj::C_HEAP, mtModule) GrowableArray<ModuleEntry*>(QUAL_EXP_SIZE, true);
+ _qualified_exports = new (ResourceObj::C_HEAP, mtModule) GrowableArray<ModuleEntry*>(QUAL_EXP_SIZE, true);
}
+
+ // Determine, based on this newly established export to module m,
+ // if this package's export list should be walked at a GC safepoint.
+ set_export_walk_required(m->loader_data());
+
+ // Establish exportability to module m
_qualified_exports->append_if_missing(m);
}
+// If the module's loader, that an export is being established to, is
+// not the same loader as this module's and is not one of the 3 builtin
+// class loaders, then this package's export list must be walked at GC
+// safepoint. Modules have the same life cycle as their defining class
+// loaders and should be removed if dead.
+void PackageEntry::set_export_walk_required(ClassLoaderData* m_loader_data) {
+ assert_locked_or_safepoint(Module_lock);
+ ModuleEntry* this_pkg_mod = module();
+ if (!_must_walk_exports &&
+ (this_pkg_mod == NULL || this_pkg_mod->loader_data() != m_loader_data) &&
+ !m_loader_data->is_builtin_class_loader_data()) {
+ _must_walk_exports = true;
+ if (log_is_enabled(Trace, modules)) {
+ ResourceMark rm;
+ assert(name() != NULL, "PackageEntry without a valid name");
+ log_trace(modules)("PackageEntry::set_export_walk_required(): package %s defined in module %s, exports list must be walked",
+ name()->as_C_string(),
+ (this_pkg_mod == NULL || this_pkg_mod->name() == NULL) ?
+ UNNAMED_MODULE : this_pkg_mod->name()->as_C_string());
+ }
+ }
+}
+
// Set the package's exported states based on the value of the ModuleEntry.
void PackageEntry::set_exported(ModuleEntry* m) {
MutexLocker m1(Module_lock);
@@ -96,14 +125,34 @@
// Remove dead module entries within the package's exported list.
void PackageEntry::purge_qualified_exports() {
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
- if (_qualified_exports != NULL) {
+ if (_must_walk_exports &&
+ _qualified_exports != NULL &&
+ !_qualified_exports->is_empty()) {
+ ModuleEntry* pkg_module = module();
+
+ // This package's _must_walk_exports flag will be reset based
+ // on the remaining live modules on the exports list.
+ _must_walk_exports = false;
+
+ if (log_is_enabled(Trace, modules)) {
+ ResourceMark rm;
+ assert(name() != NULL, "PackageEntry without a valid name");
+ ModuleEntry* pkg_mod = module();
+ log_trace(modules)("PackageEntry::purge_qualified_exports(): package %s defined in module %s, exports list being walked",
+ name()->as_C_string(),
+ (pkg_mod == NULL || pkg_mod->name() == NULL) ? UNNAMED_MODULE : pkg_mod->name()->as_C_string());
+ }
+
// Go backwards because this removes entries that are dead.
int len = _qualified_exports->length();
for (int idx = len - 1; idx >= 0; idx--) {
ModuleEntry* module_idx = _qualified_exports->at(idx);
- ClassLoaderData* cld = module_idx->loader();
- if (cld->is_unloading()) {
+ ClassLoaderData* cld_idx = module_idx->loader_data();
+ if (cld_idx->is_unloading()) {
_qualified_exports->delete_at(idx);
+ } else {
+ // Update the need to walk this package's exports based on live modules
+ set_export_walk_required(cld_idx);
}
}
}
--- a/hotspot/src/share/vm/classfile/packageEntry.hpp Tue Jun 28 10:37:52 2016 +0200
+++ b/hotspot/src/share/vm/classfile/packageEntry.hpp Tue Jun 28 10:11:01 2016 -0400
@@ -69,6 +69,7 @@
s2 _classpath_index;
bool _is_exported_unqualified;
bool _is_exported_allUnnamed;
+ bool _must_walk_exports;
GrowableArray<ModuleEntry*>* _exported_pending_delete; // transitioned from qualified to unqualified, delete at safepoint
GrowableArray<ModuleEntry*>* _qualified_exports;
TRACE_DEFINE_TRACE_ID_FIELD;
@@ -82,6 +83,7 @@
_classpath_index = -1;
_is_exported_unqualified = false;
_is_exported_allUnnamed = false;
+ _must_walk_exports = false;
_exported_pending_delete = NULL;
_qualified_exports = NULL;
}
@@ -147,6 +149,7 @@
// add the module to the package's qualified exports
void add_qexport(ModuleEntry* m);
+ void set_export_walk_required(ClassLoaderData* m_loader_data);
PackageEntry* next() const {
return (PackageEntry*)HashtableEntry<Symbol*, mtModule>::next();
--- a/hotspot/src/share/vm/classfile/systemDictionary.cpp Tue Jun 28 10:37:52 2016 +0200
+++ b/hotspot/src/share/vm/classfile/systemDictionary.cpp Tue Jun 28 10:11:01 2016 -0400
@@ -175,9 +175,18 @@
return false;
}
-/**
- * Returns true if the passed class loader is the platform class loader.
- */
+// Returns true if the passed class loader is the builtin application class loader
+// or a custom system class loader. A customer system class loader can be
+// specified via -Djava.system.class.loader.
+bool SystemDictionary::is_system_class_loader(Handle class_loader) {
+ if (class_loader.is_null()) {
+ return false;
+ }
+ return (class_loader->klass() == SystemDictionary::jdk_internal_loader_ClassLoaders_AppClassLoader_klass() ||
+ class_loader() == _java_system_loader);
+}
+
+// Returns true if the passed class loader is the platform class loader.
bool SystemDictionary::is_platform_class_loader(Handle class_loader) {
if (class_loader.is_null()) {
return false;
--- a/hotspot/src/share/vm/classfile/systemDictionary.hpp Tue Jun 28 10:37:52 2016 +0200
+++ b/hotspot/src/share/vm/classfile/systemDictionary.hpp Tue Jun 28 10:11:01 2016 -0400
@@ -660,6 +660,7 @@
static instanceKlassHandle load_shared_class(Symbol* class_name,
Handle class_loader,
TRAPS);
+ static bool is_system_class_loader(Handle class_loader);
static bool is_platform_class_loader(Handle class_loader);
protected:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/modules/ModuleStress/CustomSystemClassLoader.java Tue Jun 28 10:11:01 2016 -0400
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016, 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.
+ */
+
+/**
+ * A custom system ClassLoader to define the module "m2" to during iterations of
+ * differing test runs within the test ModuleStress.java
+ */
+public class CustomSystemClassLoader extends ClassLoader {
+ public CustomSystemClassLoader() {
+ super();
+ }
+ public CustomSystemClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/modules/ModuleStress/ModuleNonBuiltinCLMain.java Tue Jun 28 10:11:01 2016 -0400
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2016, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 static jdk.test.lib.Asserts.*;
+
+import java.lang.reflect.Layer;
+import java.lang.module.Configuration;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+//
+// ClassLoader1 --> defines m1 --> packages p1
+// ClassLoader2 --> defines m2 --> packages p2
+// Java System Class Loader --> defines m3 --> packages p3
+//
+// m1 can read m2
+// package p2 in m2 is exported to m1 and m3
+//
+// class p1.c1 defined in m1 tries to access p2.c2 defined in m2
+// Access allowed since m1 can read m2 and package p2 is exported to m1.
+//
+public class ModuleNonBuiltinCLMain {
+
+ // Create a Layer over the boot layer.
+ // Define modules within this layer to test access between
+ // publically defined classes within packages of those modules.
+ public void createLayerOnBoot() throws Throwable {
+
+ // Define module: m1
+ // Can read: java.base, m2
+ // Packages: p1
+ // Packages exported: p1 is exported to unqualifiedly
+ ModuleDescriptor descriptor_m1 =
+ new ModuleDescriptor.Builder("m1")
+ .requires("java.base")
+ .requires("m2")
+ .exports("p1")
+ .build();
+
+ // Define module: m2
+ // Can read: java.base, m3
+ // Packages: p2
+ // Packages exported: package p2 is exported to m1 and m3
+ Set<String> targets = new HashSet<>();
+ targets.add("m1");
+ targets.add("m3");
+ ModuleDescriptor descriptor_m2 =
+ new ModuleDescriptor.Builder("m2")
+ .requires("java.base")
+ .requires("m3")
+ .exports("p2", targets)
+ .build();
+
+ // Define module: m3
+ // Can read: java.base
+ // Packages: p3
+ // Packages exported: none
+ ModuleDescriptor descriptor_m3 =
+ new ModuleDescriptor.Builder("m3")
+ .requires("java.base")
+ .build();
+
+ // Set up a ModuleFinder containing all modules for this layer.
+ ModuleFinder finder = ModuleLibrary.of(descriptor_m1, descriptor_m2, descriptor_m3);
+
+ // Resolves "m1"
+ Configuration cf = Layer.boot()
+ .configuration()
+ .resolveRequires(finder, ModuleFinder.of(), Set.of("m1"));
+
+ // map each module to differing user defined class loaders for this test
+ Map<String, ClassLoader> map = new HashMap<>();
+ Loader1 cl1 = new Loader1();
+ Loader2 cl2 = new Loader2();
+ ClassLoader cl3 = ClassLoader.getSystemClassLoader();
+ map.put("m1", cl1);
+ map.put("m2", cl2);
+ map.put("m3", cl3);
+
+ // Create Layer that contains m1 & m2
+ Layer layer = Layer.boot().defineModules(cf, map::get);
+ assertTrue(layer.findLoader("m1") == cl1);
+ assertTrue(layer.findLoader("m2") == cl2);
+ assertTrue(layer.findLoader("m3") == cl3);
+ assertTrue(layer.findLoader("java.base") == null);
+
+ // now use the same loader to load class p1.c1
+ Class p1_c1_class = cl1.loadClass("p1.c1");
+ try {
+ p1_c1_class.newInstance();
+ } catch (IllegalAccessError e) {
+ throw new RuntimeException("Test Failed, an IAE should not be thrown since p2 is exported qualifiedly to m1");
+ }
+ }
+
+ public static void main(String args[]) throws Throwable {
+ ModuleNonBuiltinCLMain test = new ModuleNonBuiltinCLMain();
+ test.createLayerOnBoot();
+ }
+
+ static class Loader1 extends ClassLoader { }
+ static class Loader2 extends ClassLoader { }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/modules/ModuleStress/ModuleSameCLMain.java Tue Jun 28 10:11:01 2016 -0400
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2016, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 static jdk.test.lib.Asserts.*;
+
+import java.lang.reflect.Layer;
+import java.lang.module.Configuration;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+//
+// ClassLoader1 --> defines m1 --> packages p1
+// ClassLoader1 --> defines m2 --> packages p2
+//
+// m1 can read m2
+// package p2 in m2 is exported to m1
+//
+// class p1.c1 defined in m1 tries to access p2.c2 defined in m2
+// Access allowed since m1 can read m2 and package p2 is exported to m1.
+//
+public class ModuleSameCLMain {
+
+ // Create a Layer over the boot layer.
+ // Define modules within this layer to test access between
+ // publically defined classes within packages of those modules.
+ public void createLayerOnBoot() throws Throwable {
+
+ // Define module: m1
+ // Can read: java.base, m2
+ // Packages: p1
+ // Packages exported: p1 is exported to unqualifiedly
+ ModuleDescriptor descriptor_m1 =
+ new ModuleDescriptor.Builder("m1")
+ .requires("java.base")
+ .requires("m2")
+ .exports("p1")
+ .build();
+
+ // Define module: m2
+ // Can read: java.base
+ // Packages: p2
+ // Packages exported: package p2 is exported to m1
+ ModuleDescriptor descriptor_m2 =
+ new ModuleDescriptor.Builder("m2")
+ .requires("java.base")
+ .exports("p2", "m1")
+ .build();
+
+ // Set up a ModuleFinder containing all modules for this layer.
+ ModuleFinder finder = ModuleLibrary.of(descriptor_m1, descriptor_m2);
+
+ // Resolves "m1"
+ Configuration cf = Layer.boot()
+ .configuration()
+ .resolveRequires(finder, ModuleFinder.of(), Set.of("m1"));
+
+ // map each module to the same class loader for this test
+ Map<String, ClassLoader> map = new HashMap<>();
+ Loader1 cl1 = new Loader1();
+ map.put("m1", cl1);
+ map.put("m2", cl1);
+
+ // Create Layer that contains m1 & m2
+ Layer layer = Layer.boot().defineModules(cf, map::get);
+ assertTrue(layer.findLoader("m1") == cl1);
+ assertTrue(layer.findLoader("m2") == cl1);
+ assertTrue(layer.findLoader("java.base") == null);
+
+ // now use the same loader to load class p1.c1
+ Class p1_c1_class = cl1.loadClass("p1.c1");
+ try {
+ p1_c1_class.newInstance();
+ } catch (IllegalAccessError e) {
+ throw new RuntimeException("Test Failed, an IAE should not be thrown since p2 is exported qualifiedly to m1");
+ }
+ }
+
+ public static void main(String args[]) throws Throwable {
+ ModuleSameCLMain test = new ModuleSameCLMain();
+ test.createLayerOnBoot();
+ }
+
+ static class Loader1 extends ClassLoader { }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/modules/ModuleStress/ModuleStress.java Tue Jun 28 10:11:01 2016 -0400
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2016, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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
+ * @bug 8159262
+ * @summary Test differing scenarios where a module's readability list and a package's exportability list should be walked
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary /test/lib
+ * @compile ../AccessCheck/ModuleLibrary.java
+ * @compile ModuleSameCLMain.java
+ * @compile ModuleNonBuiltinCLMain.java
+ * @compile CustomSystemClassLoader.java
+ * @build ModuleStress
+ * @run main/othervm ModuleStress
+ */
+
+import jdk.test.lib.*;
+import java.io.File;
+
+public class ModuleStress {
+
+ public static void main(String[] args) throws Exception {
+
+ // Test #1: java -version
+ // All modules' readability lists and packages' exportability
+ // lists should contain only modules defined to the 3 builtin
+ // loaders (boot, application, platform). Thus there is
+ // not a need to walk those lists at a GC safepoint since
+ // those loaders never die.
+ ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
+ "-Xbootclasspath/a:.",
+ "-Xlog:modules=trace",
+ "-version");
+
+ OutputAnalyzer oa = new OutputAnalyzer(pb.start());
+ oa.shouldNotContain("must be walked")
+ .shouldNotContain("being walked")
+ .shouldHaveExitValue(0);
+
+ // Next 2 tests involve the use of class p1.c1 and p2.c2
+ String source1 = "package p1;" +
+ "import p2.c2;" +
+ "public class c1 {" +
+ " public c1() {" +
+ " p2.c2 c2_obj = new p2.c2();" +
+ " c2_obj.method2();" +
+ " }" +
+ "}";
+
+ String source2 = "package p2;" +
+ "public class c2 {" +
+ " public void method2() { }" +
+ "}";
+
+ ClassFileInstaller.writeClassToDisk("p2/c2",
+ InMemoryJavaCompiler.compile("p2.c2", source2), System.getProperty("test.classes"));
+
+ ClassFileInstaller.writeClassToDisk("p1/c1",
+ InMemoryJavaCompiler.compile("p1.c1", source1), System.getProperty("test.classes"));
+
+ // Test #2: Load two modules defined to the same customer class loader.
+ // m1's module readability list and package p2's exportability should
+ // not be walked at a GC safepoint since both modules are defined to
+ // the same loader and thus have the exact same life cycle.
+ pb = ProcessTools.createJavaProcessBuilder(
+ "-Xbootclasspath/a:.",
+ "-Xlog:modules=trace",
+ "ModuleSameCLMain");
+
+ oa = new OutputAnalyzer(pb.start());
+ oa.shouldNotContain("must be walked")
+ .shouldNotContain("being walked")
+ .shouldHaveExitValue(0);
+
+ // Test #3: Load two modules in differing custom class loaders.
+ // m1's module readability list and package p2's exportability list must
+ // be walked at a GC safepoint since both modules are defined to non-builtin
+ // class loaders which could die and thus be unloaded.
+ pb = ProcessTools.createJavaProcessBuilder(
+ "-Xbootclasspath/a:.",
+ "-Xlog:modules=trace",
+ "ModuleNonBuiltinCLMain");
+
+ oa = new OutputAnalyzer(pb.start());
+ oa.shouldContain("module m1 reads list must be walked")
+ .shouldContain("package p2 defined in module m2, exports list must be walked")
+ .shouldNotContain("module m2 reads list must be walked")
+ .shouldHaveExitValue(0);
+
+ // Test #4: Load two modules in differing custom class loaders,
+ // of which one has been designated as the custom system class loader
+ // via -Djava.system.class.loader=CustomSystemClassLoader. Since
+ // m3 is defined to the system class loader, m2's module readability
+ // list does not have to be walked at a GC safepoint, but package p2's
+ // exportability list does.
+ pb = ProcessTools.createJavaProcessBuilder(
+ "-Djava.system.class.loader=CustomSystemClassLoader",
+ "-Xbootclasspath/a:.",
+ "-Xlog:modules=trace",
+ "ModuleNonBuiltinCLMain");
+
+ oa = new OutputAnalyzer(pb.start());
+ oa.shouldContain("package p2 defined in module m2, exports list must be walked")
+ .shouldNotContain("module m2 reads list must be walked")
+ .shouldHaveExitValue(0);
+
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/modules/ModuleStress/ModuleStressGC.java Tue Jun 28 10:11:01 2016 -0400
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2016, 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
+ * @bug 8159262
+ * @summary layers over the boot layer are repeatedly created, during this iteration, GCs are forced to verify correct walk of module and package lists.
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary /test/lib
+ * @compile ../CompilerUtils.java
+ * @build ModuleStressGC
+ * @run main/othervm ModuleStressGC
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import jdk.test.lib.*;
+
+public class ModuleStressGC {
+
+ private static final String TEST_SRC = System.getProperty("test.src");
+ private static final String TEST_CLASSES = System.getProperty("test.classes");
+
+ private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
+ private static final Path MODS_DIR = Paths.get(TEST_CLASSES, "mods");
+
+ /**
+ * Compile two module definitions used by the test, jdk.test and jdk.translet.
+ */
+ public static void main(String[] args) throws Exception {
+
+ boolean compiled;
+ // Compile module jdk.test declaration
+ compiled = CompilerUtils.compile(
+ SRC_DIR.resolve("jdk.test"),
+ MODS_DIR.resolve("jdk.test"));
+ if (!compiled) {
+ throw new RuntimeException("Test failed to compile module jdk.test");
+ }
+
+ // Compile module jdk.translet declaration
+ compiled = CompilerUtils.compile(
+ SRC_DIR.resolve("jdk.translet"),
+ MODS_DIR.resolve("jdk.translet"),
+ "-XaddExports:jdk.test/test=jdk.translet",
+ "-mp", MODS_DIR.toString());
+ if (!compiled) {
+ throw new RuntimeException("Test failed to compile module jdk.translet");
+ }
+
+ // Sanity check that the test, jdk.test/test/MainGC.java,
+ // correctly walks module jdk.test's reads list and package
+ // test's, defined to module jdk.translet, export list at
+ // GC safepoints.
+ ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
+ "-Xlog:modules=trace",
+ "-mp", MODS_DIR.toString(),
+ "-m", "jdk.test/test.MainGC");
+ OutputAnalyzer oa = new OutputAnalyzer(pb.start());
+ oa.shouldContain("package test defined in module jdk.test, exports list being walked")
+ .shouldContain("module jdk.test reads list being walked")
+ .shouldHaveExitValue(0);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/modules/ModuleStress/src/jdk.test/test/MainGC.java Tue Jun 28 10:11:01 2016 -0400
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2016, 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 test;
+
+import java.lang.module.Configuration;
+import java.lang.module.ModuleFinder;
+import java.lang.reflect.Layer;
+import java.lang.reflect.Method;
+import java.lang.reflect.Module;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class MainGC {
+
+ private static final Path MODS_DIR = Paths.get(System.getProperty("jdk.module.path"));
+ static final String MODULE_NAME = "jdk.translet";
+
+ public static void main(String[] args) throws Exception {
+
+ ModuleFinder finder = ModuleFinder.of(MODS_DIR);
+ Layer layerBoot = Layer.boot();
+
+ Configuration cf = layerBoot
+ .configuration()
+ .resolveRequires(ModuleFinder.of(), finder, Set.of(MODULE_NAME));
+
+ Module testModule = MainGC.class.getModule();
+ ClassLoader scl = ClassLoader.getSystemClassLoader();
+
+ // Create an unique module/class loader in a layer above the boot layer.
+ // Export this module to the jdk.test/test package.
+ // Add a read edge from module jdk.test to this module.
+ Callable<Void> task = new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ Layer layer = Layer.boot().defineModulesWithOneLoader(cf, scl);
+ Module transletModule = layer.findModule(MODULE_NAME).get();
+ testModule.addExports("test", transletModule);
+ testModule.addReads(transletModule);
+ Class<?> c = layer.findLoader(MODULE_NAME).loadClass("translet.MainGC");
+ Method method = c.getDeclaredMethod("go");
+ method.invoke(null);
+ return null;
+ }
+ };
+
+ List<Future<Void>> results = new ArrayList<>();
+
+ // Repeatedly create the layer above stressing the exportation of
+ // package jdk.test/test to several different modules.
+ ExecutorService pool = Executors.newFixedThreadPool(Math.min(100, Runtime.getRuntime().availableProcessors()*10));
+ try {
+ for (int i = 0; i < 10000; i++) {
+ results.add(pool.submit(task));
+ // At specified intervals, force a GC. This provides an
+ // opportunity to verify that both the module jdk.test's reads
+ // and the package test's, which is defined to jdk.test, exports
+ // lists are being walked.
+ if (i == 3000 || i == 6000 || i == 9000) {
+ System.gc();
+ }
+ }
+ } finally {
+ pool.shutdown();
+ }
+
+ int passed = 0;
+ int failed = 0;
+
+ // The failed state should be 0, the created modules in layers above the
+ // boot layer should be allowed access to the contents of the jdk.test/test
+ // package since that package was exported to the transletModule above.
+ for (Future<Void> result : results) {
+ try {
+ result.get();
+ passed++;
+ } catch (Throwable x) {
+ x.printStackTrace();
+ failed++;
+ }
+ }
+
+ System.out.println("passed: " + passed);
+ System.out.println("failed: " + failed);
+ }
+
+ public static void callback() { }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/modules/ModuleStress/src/jdk.translet/translet/MainGC.java Tue Jun 28 10:11:01 2016 -0400
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2016, 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 translet;
+
+public class MainGC {
+ public static void go() {
+ test.MainGC.callback();
+ }
+}