mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 23:04:50 +02:00
8000662: NPG: nashorn ant clean test262 out-of-memory with Java heap
Add ClassLoaderData object for each anonymous class with metaspaces to allocate in. Reviewed-by: twisti, jrose, stefank
This commit is contained in:
parent
c00c803b89
commit
7aa43fc5d8
26 changed files with 365 additions and 249 deletions
|
@ -65,13 +65,19 @@
|
|||
ClassLoaderData * ClassLoaderData::_the_null_class_loader_data = NULL;
|
||||
|
||||
ClassLoaderData::ClassLoaderData(Handle h_class_loader) : _class_loader(h_class_loader()),
|
||||
_metaspace(NULL), _unloading(false), _klasses(NULL),
|
||||
_claimed(0), _jmethod_ids(NULL), _handles(NULL),
|
||||
_deallocate_list(NULL), _next(NULL),
|
||||
_metaspace(NULL), _unloading(false), _keep_alive(false), _klasses(NULL),
|
||||
_claimed(0), _jmethod_ids(NULL), _handles(NULL), _deallocate_list(NULL),
|
||||
_next(NULL), _dependencies(NULL),
|
||||
_metaspace_lock(new Mutex(Monitor::leaf+1, "Metaspace allocation lock", true)) {
|
||||
// empty
|
||||
}
|
||||
|
||||
void ClassLoaderData::init_dependencies(TRAPS) {
|
||||
// Create empty dependencies array to add to. CMS requires this to be
|
||||
// an oop so that it can track additions via card marks. We think.
|
||||
_dependencies = (oop)oopFactory::new_objectArray(2, CHECK);
|
||||
}
|
||||
|
||||
bool ClassLoaderData::claim() {
|
||||
if (_claimed == 1) {
|
||||
return false;
|
||||
|
@ -86,6 +92,7 @@ void ClassLoaderData::oops_do(OopClosure* f, KlassClosure* klass_closure, bool m
|
|||
}
|
||||
|
||||
f->do_oop(&_class_loader);
|
||||
f->do_oop(&_dependencies);
|
||||
_handles->oops_do(f);
|
||||
if (klass_closure != NULL) {
|
||||
classes_do(klass_closure);
|
||||
|
@ -110,69 +117,99 @@ void ClassLoaderData::record_dependency(Klass* k, TRAPS) {
|
|||
ClassLoaderData * const from_cld = this;
|
||||
ClassLoaderData * const to_cld = k->class_loader_data();
|
||||
|
||||
// Records dependency between non-null class loaders only.
|
||||
if (to_cld->is_the_null_class_loader_data() || from_cld->is_the_null_class_loader_data()) {
|
||||
// Dependency to the null class loader data doesn't need to be recorded
|
||||
// because the null class loader data never goes away.
|
||||
if (to_cld->is_the_null_class_loader_data()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that this dependency isn't from the same or parent class_loader
|
||||
oop to = to_cld->class_loader();
|
||||
oop from = from_cld->class_loader();
|
||||
oop to;
|
||||
if (to_cld->is_anonymous()) {
|
||||
// Anonymous class dependencies are through the mirror.
|
||||
to = k->java_mirror();
|
||||
} else {
|
||||
to = to_cld->class_loader();
|
||||
|
||||
oop curr = from;
|
||||
while (curr != NULL) {
|
||||
if (curr == to) {
|
||||
return; // this class loader is in the parent list, no need to add it.
|
||||
// If from_cld is anonymous, even if it's class_loader is a parent of 'to'
|
||||
// we still have to add it. The class_loader won't keep from_cld alive.
|
||||
if (!from_cld->is_anonymous()) {
|
||||
// Check that this dependency isn't from the same or parent class_loader
|
||||
oop from = from_cld->class_loader();
|
||||
|
||||
oop curr = from;
|
||||
while (curr != NULL) {
|
||||
if (curr == to) {
|
||||
return; // this class loader is in the parent list, no need to add it.
|
||||
}
|
||||
curr = java_lang_ClassLoader::parent(curr);
|
||||
}
|
||||
}
|
||||
curr = java_lang_ClassLoader::parent(curr);
|
||||
}
|
||||
|
||||
// It's a dependency we won't find through GC, add it. This is relatively rare
|
||||
from_cld->add_dependency(to_cld, CHECK);
|
||||
// Must handle over GC point.
|
||||
Handle dependency(THREAD, to);
|
||||
from_cld->add_dependency(dependency, CHECK);
|
||||
}
|
||||
|
||||
bool ClassLoaderData::has_dependency(ClassLoaderData* dependency) {
|
||||
oop loader = dependency->class_loader();
|
||||
|
||||
// Get objArrayOop out of the class_loader oop and see if this dependency
|
||||
// is there. Don't safepoint! These are all oops.
|
||||
// Dependency list is (oop class_loader, objArrayOop next)
|
||||
objArrayOop ok = (objArrayOop)java_lang_ClassLoader::dependencies(class_loader());
|
||||
void ClassLoaderData::add_dependency(Handle dependency, TRAPS) {
|
||||
// Check first if this dependency is already in the list.
|
||||
// Save a pointer to the last to add to under the lock.
|
||||
objArrayOop ok = (objArrayOop)_dependencies;
|
||||
objArrayOop last = NULL;
|
||||
while (ok != NULL) {
|
||||
if (ok->obj_at(0) == loader) {
|
||||
return true;
|
||||
last = ok;
|
||||
if (ok->obj_at(0) == dependency()) {
|
||||
// Don't need to add it
|
||||
return;
|
||||
}
|
||||
ok = (objArrayOop)ok->obj_at(1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClassLoaderData::add_dependency(ClassLoaderData* dependency, TRAPS) {
|
||||
// Minimize the number of duplicates in the list.
|
||||
if (has_dependency(dependency)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new dependency node with fields for (class_loader, next)
|
||||
// Create a new dependency node with fields for (class_loader or mirror, next)
|
||||
objArrayOop deps = oopFactory::new_objectArray(2, CHECK);
|
||||
deps->obj_at_put(0, dependency->class_loader());
|
||||
deps->obj_at_put(0, dependency());
|
||||
|
||||
// Add this lock free, using compare and exchange, need barriers for GC
|
||||
// Do the barrier first.
|
||||
HeapWord* addr = java_lang_ClassLoader::dependencies_addr(class_loader());
|
||||
while (true) {
|
||||
oop old_dependency = java_lang_ClassLoader::dependencies(class_loader());
|
||||
deps->obj_at_put(1, old_dependency);
|
||||
// Must handle over more GC points
|
||||
objArrayHandle new_dependency(THREAD, deps);
|
||||
|
||||
oop newold = oopDesc::atomic_compare_exchange_oop((oop)deps, addr, old_dependency, true);
|
||||
if (newold == old_dependency) {
|
||||
update_barrier_set((void*)addr, (oop)deps);
|
||||
// we won the race to add this dependency
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Add the dependency under lock
|
||||
assert (last != NULL, "dependencies should be initialized");
|
||||
objArrayHandle last_handle(THREAD, last);
|
||||
locked_add_dependency(last_handle, new_dependency);
|
||||
}
|
||||
|
||||
void ClassLoaderData::locked_add_dependency(objArrayHandle last_handle,
|
||||
objArrayHandle new_dependency) {
|
||||
|
||||
// Have to lock and put the new dependency on the end of the dependency
|
||||
// array so the card mark for CMS sees that this dependency is new.
|
||||
// Can probably do this lock free with some effort.
|
||||
MutexLockerEx ml(metaspace_lock(), Mutex::_no_safepoint_check_flag);
|
||||
|
||||
oop loader_or_mirror = new_dependency->obj_at(0);
|
||||
|
||||
// Since the dependencies are only added, add to the end.
|
||||
objArrayOop end = last_handle();
|
||||
objArrayOop last = NULL;
|
||||
while (end != NULL) {
|
||||
last = end;
|
||||
// check again if another thread added it to the end.
|
||||
if (end->obj_at(0) == loader_or_mirror) {
|
||||
// Don't need to add it
|
||||
return;
|
||||
}
|
||||
end = (objArrayOop)end->obj_at(1);
|
||||
}
|
||||
assert (last != NULL, "dependencies should be initialized");
|
||||
// fill in the first element with the oop in new_dependency.
|
||||
if (last->obj_at(0) == NULL) {
|
||||
last->obj_at_put(0, new_dependency->obj_at(0));
|
||||
} else {
|
||||
last->obj_at_put(1, new_dependency());
|
||||
}
|
||||
}
|
||||
|
||||
void ClassLoaderDataGraph::clear_claimed_marks() {
|
||||
for (ClassLoaderData* cld = _head; cld != NULL; cld = cld->next()) {
|
||||
|
@ -187,7 +224,7 @@ void ClassLoaderData::add_class(Klass* k) {
|
|||
// link the new item into the list
|
||||
_klasses = k;
|
||||
|
||||
if (TraceClassLoaderData && k->class_loader_data() != NULL) {
|
||||
if (TraceClassLoaderData && Verbose && k->class_loader_data() != NULL) {
|
||||
ResourceMark rm;
|
||||
tty->print_cr("[TraceClassLoaderData] Adding k: " PTR_FORMAT " %s to CLD: "
|
||||
PTR_FORMAT " loader: " PTR_FORMAT " %s",
|
||||
|
@ -195,8 +232,7 @@ void ClassLoaderData::add_class(Klass* k) {
|
|||
k->external_name(),
|
||||
k->class_loader_data(),
|
||||
k->class_loader(),
|
||||
k->class_loader() != NULL ? k->class_loader()->klass()->external_name() : "NULL"
|
||||
);
|
||||
loader_name());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,6 +257,38 @@ void ClassLoaderData::remove_class(Klass* scratch_class) {
|
|||
ShouldNotReachHere(); // should have found this class!!
|
||||
}
|
||||
|
||||
|
||||
bool ClassLoaderData::is_anonymous() const {
|
||||
Klass* k = _klasses;
|
||||
return (_keep_alive || (k != NULL && k->oop_is_instance() &&
|
||||
InstanceKlass::cast(k)->is_anonymous()));
|
||||
}
|
||||
|
||||
void ClassLoaderData::unload() {
|
||||
_unloading = true;
|
||||
|
||||
if (TraceClassLoaderData) {
|
||||
ResourceMark rm;
|
||||
tty->print("[ClassLoaderData: unload loader data "PTR_FORMAT, this);
|
||||
tty->print(" for instance "PTR_FORMAT" of %s", class_loader(),
|
||||
loader_name());
|
||||
if (is_anonymous()) {
|
||||
tty->print(" for anonymous class "PTR_FORMAT " ", _klasses);
|
||||
}
|
||||
tty->print_cr("]");
|
||||
}
|
||||
}
|
||||
|
||||
bool ClassLoaderData::is_alive(BoolObjectClosure* is_alive_closure) const {
|
||||
bool alive =
|
||||
is_anonymous() ?
|
||||
is_alive_closure->do_object_b(_klasses->java_mirror()) :
|
||||
class_loader() == NULL || is_alive_closure->do_object_b(class_loader());
|
||||
assert(!alive || claimed(), "must be claimed");
|
||||
return alive;
|
||||
}
|
||||
|
||||
|
||||
ClassLoaderData::~ClassLoaderData() {
|
||||
Metaspace *m = _metaspace;
|
||||
if (m != NULL) {
|
||||
|
@ -263,8 +331,8 @@ Metaspace* ClassLoaderData::metaspace_non_null() {
|
|||
if (_metaspace != NULL) {
|
||||
return _metaspace;
|
||||
}
|
||||
if (class_loader() == NULL) {
|
||||
assert(this == the_null_class_loader_data(), "Must be");
|
||||
if (this == the_null_class_loader_data()) {
|
||||
assert (class_loader() == NULL, "Must be");
|
||||
size_t word_size = Metaspace::first_chunk_word_size();
|
||||
set_metaspace(new Metaspace(_metaspace_lock, word_size));
|
||||
} else {
|
||||
|
@ -325,12 +393,19 @@ void ClassLoaderData::free_deallocate_list() {
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef PRODUCT
|
||||
void ClassLoaderData::print_loader(ClassLoaderData *loader_data, outputStream* out) {
|
||||
oop class_loader = loader_data->class_loader();
|
||||
out->print("%s", SystemDictionary::loader_name(class_loader));
|
||||
// These anonymous class loaders are to contain classes used for JSR292
|
||||
ClassLoaderData* ClassLoaderData::anonymous_class_loader_data(oop loader, TRAPS) {
|
||||
// Add a new class loader data to the graph.
|
||||
ClassLoaderData* cld = ClassLoaderDataGraph::add(NULL, loader, CHECK_NULL);
|
||||
return cld;
|
||||
}
|
||||
|
||||
const char* ClassLoaderData::loader_name() {
|
||||
// Handles null class loader
|
||||
return SystemDictionary::loader_name(class_loader());
|
||||
}
|
||||
|
||||
#ifndef PRODUCT
|
||||
// Define to dump klasses
|
||||
#undef CLD_DUMP_KLASSES
|
||||
|
||||
|
@ -338,8 +413,7 @@ void ClassLoaderData::dump(outputStream * const out) {
|
|||
ResourceMark rm;
|
||||
out->print("ClassLoaderData CLD: "PTR_FORMAT", loader: "PTR_FORMAT", loader_klass: "PTR_FORMAT" %s {",
|
||||
this, class_loader(),
|
||||
class_loader() != NULL ? class_loader()->klass() : NULL,
|
||||
class_loader() != NULL ? class_loader()->klass()->external_name() : "NULL");
|
||||
class_loader() != NULL ? class_loader()->klass() : NULL, loader_name());
|
||||
if (claimed()) out->print(" claimed ");
|
||||
if (is_unloading()) out->print(" unloading ");
|
||||
out->print(" handles " INTPTR_FORMAT, handles());
|
||||
|
@ -373,8 +447,8 @@ void ClassLoaderData::dump(outputStream * const out) {
|
|||
void ClassLoaderData::verify() {
|
||||
oop cl = class_loader();
|
||||
|
||||
guarantee(this == class_loader_data(cl), "Must be the same");
|
||||
guarantee(cl != NULL || this == ClassLoaderData::the_null_class_loader_data(), "must be");
|
||||
guarantee(this == class_loader_data(cl) || is_anonymous(), "Must be the same");
|
||||
guarantee(cl != NULL || this == ClassLoaderData::the_null_class_loader_data() || is_anonymous(), "must be");
|
||||
|
||||
// Verify the integrity of the allocated space.
|
||||
if (metaspace_or_null() != NULL) {
|
||||
|
@ -387,6 +461,7 @@ void ClassLoaderData::verify() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// GC root of class loader data created.
|
||||
ClassLoaderData* ClassLoaderDataGraph::_head = NULL;
|
||||
ClassLoaderData* ClassLoaderDataGraph::_unloading = NULL;
|
||||
|
@ -395,19 +470,25 @@ ClassLoaderData* ClassLoaderDataGraph::_saved_head = NULL;
|
|||
|
||||
// Add a new class loader data node to the list. Assign the newly created
|
||||
// ClassLoaderData into the java/lang/ClassLoader object as a hidden field
|
||||
ClassLoaderData* ClassLoaderDataGraph::add(ClassLoaderData** cld_addr, Handle loader_data) {
|
||||
ClassLoaderData* ClassLoaderDataGraph::add(ClassLoaderData** cld_addr, Handle loader, TRAPS) {
|
||||
// Not assigned a class loader data yet.
|
||||
// Create one.
|
||||
ClassLoaderData* *list_head = &_head;
|
||||
ClassLoaderData* next = _head;
|
||||
ClassLoaderData* cld = new ClassLoaderData(loader_data);
|
||||
ClassLoaderData* cld = new ClassLoaderData(loader);
|
||||
|
||||
// First, Atomically set it.
|
||||
ClassLoaderData* old = (ClassLoaderData*) Atomic::cmpxchg_ptr(cld, cld_addr, NULL);
|
||||
if (old != NULL) {
|
||||
delete cld;
|
||||
// Returns the data.
|
||||
return old;
|
||||
if (cld_addr != NULL) {
|
||||
// First, Atomically set it
|
||||
ClassLoaderData* old = (ClassLoaderData*) Atomic::cmpxchg_ptr(cld, cld_addr, NULL);
|
||||
if (old != NULL) {
|
||||
delete cld;
|
||||
// Returns the data.
|
||||
return old;
|
||||
}
|
||||
} else {
|
||||
// Disallow unloading for this CLD during initialization if there is no
|
||||
// class_loader oop to link this to.
|
||||
cld->set_keep_alive(true);
|
||||
}
|
||||
|
||||
// We won the race, and therefore the task of adding the data to the list of
|
||||
|
@ -417,16 +498,22 @@ ClassLoaderData* ClassLoaderDataGraph::add(ClassLoaderData** cld_addr, Handle lo
|
|||
ClassLoaderData* exchanged = (ClassLoaderData*)Atomic::cmpxchg_ptr(cld, list_head, next);
|
||||
if (exchanged == next) {
|
||||
if (TraceClassLoaderData) {
|
||||
ResourceMark rm;
|
||||
tty->print("[ClassLoaderData: ");
|
||||
tty->print("create class loader data "PTR_FORMAT, cld);
|
||||
tty->print(" for instance "PTR_FORMAT" of ", cld->class_loader());
|
||||
loader_data->klass()->name()->print_symbol_on(tty);
|
||||
tty->print(" for instance "PTR_FORMAT" of %s", cld->class_loader(),
|
||||
cld->loader_name());
|
||||
tty->print_cr("]");
|
||||
}
|
||||
// Create dependencies after the CLD is added to the list. Otherwise,
|
||||
// the GC GC will not find the CLD and the _class_loader field will
|
||||
// not be updated.
|
||||
cld->init_dependencies(CHECK_NULL);
|
||||
return cld;
|
||||
}
|
||||
next = exchanged;
|
||||
} while (true);
|
||||
|
||||
}
|
||||
|
||||
void ClassLoaderDataGraph::oops_do(OopClosure* f, KlassClosure* klass_closure, bool must_claim) {
|
||||
|
@ -435,9 +522,19 @@ void ClassLoaderDataGraph::oops_do(OopClosure* f, KlassClosure* klass_closure, b
|
|||
}
|
||||
}
|
||||
|
||||
void ClassLoaderDataGraph::keep_alive_oops_do(OopClosure* f, KlassClosure* klass_closure, bool must_claim) {
|
||||
for (ClassLoaderData* cld = _head; cld != NULL; cld = cld->next()) {
|
||||
if (cld->keep_alive()) {
|
||||
cld->oops_do(f, klass_closure, must_claim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClassLoaderDataGraph::always_strong_oops_do(OopClosure* f, KlassClosure* klass_closure, bool must_claim) {
|
||||
if (ClassUnloading) {
|
||||
ClassLoaderData::the_null_class_loader_data()->oops_do(f, klass_closure, must_claim);
|
||||
// keep any special CLDs alive.
|
||||
ClassLoaderDataGraph::keep_alive_oops_do(f, klass_closure, must_claim);
|
||||
} else {
|
||||
ClassLoaderDataGraph::oops_do(f, klass_closure, must_claim);
|
||||
}
|
||||
|
@ -516,9 +613,10 @@ bool ClassLoaderDataGraph::contains_loader_data(ClassLoaderData* loader_data) {
|
|||
}
|
||||
#endif // PRODUCT
|
||||
|
||||
|
||||
// Move class loader data from main list to the unloaded list for unloading
|
||||
// and deallocation later.
|
||||
bool ClassLoaderDataGraph::do_unloading(BoolObjectClosure* is_alive) {
|
||||
bool ClassLoaderDataGraph::do_unloading(BoolObjectClosure* is_alive_closure) {
|
||||
ClassLoaderData* data = _head;
|
||||
ClassLoaderData* prev = NULL;
|
||||
bool seen_dead_loader = false;
|
||||
|
@ -527,8 +625,7 @@ bool ClassLoaderDataGraph::do_unloading(BoolObjectClosure* is_alive) {
|
|||
bool has_redefined_a_class = JvmtiExport::has_redefined_a_class();
|
||||
MetadataOnStackMark md_on_stack;
|
||||
while (data != NULL) {
|
||||
if (data->class_loader() == NULL || is_alive->do_object_b(data->class_loader())) {
|
||||
assert(data->claimed(), "class loader data must have been claimed");
|
||||
if (data->keep_alive() || data->is_alive(is_alive_closure)) {
|
||||
if (has_redefined_a_class) {
|
||||
data->classes_do(InstanceKlass::purge_previous_versions);
|
||||
}
|
||||
|
@ -539,13 +636,7 @@ bool ClassLoaderDataGraph::do_unloading(BoolObjectClosure* is_alive) {
|
|||
}
|
||||
seen_dead_loader = true;
|
||||
ClassLoaderData* dead = data;
|
||||
dead->mark_for_unload();
|
||||
if (TraceClassLoaderData) {
|
||||
tty->print("[ClassLoaderData: unload loader data "PTR_FORMAT, dead);
|
||||
tty->print(" for instance "PTR_FORMAT" of ", dead->class_loader());
|
||||
dead->class_loader()->klass()->name()->print_symbol_on(tty);
|
||||
tty->print_cr("]");
|
||||
}
|
||||
dead->unload();
|
||||
data = data->next();
|
||||
// Remove from loader list.
|
||||
if (prev != NULL) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue