Skip to content

Commit

Permalink
8293156: Dcmd VM.classloaders fails to print the full hierarchy
Browse files Browse the repository at this point in the history
Reviewed-by: dholmes, cjplummer
  • Loading branch information
tstuefe committed Sep 21, 2022
1 parent 711e252 commit c6be2cd
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 54 deletions.
98 changes: 65 additions & 33 deletions src/hotspot/share/classfile/classLoaderHierarchyDCmd.cpp
Expand Up @@ -122,7 +122,7 @@ struct LoadedClassInfo : public ResourceObj {
const ClassLoaderData* const _cld;

LoadedClassInfo(Klass* klass, const ClassLoaderData* cld)
: _klass(klass), _cld(cld) {}
: _next(NULL), _klass(klass), _cld(cld) {}

};

Expand All @@ -137,7 +137,7 @@ class LoaderTreeNode : public ResourceObj {
// this parent loader, we fill in all the other details.

const oop _loader_oop;
const ClassLoaderData* _cld;
const ClassLoaderData* _cld; // May be NULL if loader never loaded anything

LoaderTreeNode* _child;
LoaderTreeNode* _next;
Expand All @@ -154,33 +154,59 @@ class LoaderTreeNode : public ResourceObj {
// one.
int _num_folded;

void print_with_childs(outputStream* st, BranchTracker& branchtracker,
bool print_classes, bool verbose) const {
// Returns Klass of loader; NULL for bootstrap loader
const Klass* loader_klass() const {
return (_loader_oop != NULL) ? _loader_oop->klass() : NULL;
}

ResourceMark rm;
// Returns ResourceArea-allocated class name of loader class; "" if there is no klass (bootstrap loader)
const char* loader_class_name() const {
const Klass* klass = loader_klass();
return klass != NULL ? klass->external_name() : "";
}

if (_cld == NULL) {
// Not sure how this could happen: we added a preliminary node for a parent but then never encountered
// its CLD?
return;
// Returns oop of loader name; NULL for bootstrap; NULL if no name was set
oop loader_name_oop() const {
return (_loader_oop != NULL) ? java_lang_ClassLoader::name(_loader_oop) : NULL;
}

// Returns ResourceArea-allocated name of loader, "" if none is set
const char* loader_name() const {
oop name_oop = loader_name_oop();
return name_oop != NULL ? java_lang_String::as_utf8_string(name_oop) : "";
}

bool is_bootstrap() const {
if (_loader_oop == NULL) {
assert(_cld != NULL && _cld->is_boot_class_loader_data(), "bootstrap loader must have CLD");
return true;
}
return false;
}

void print_with_child_nodes(outputStream* st, BranchTracker& branchtracker,
bool print_classes, bool verbose) const {

assert(SafepointSynchronize::is_at_safepoint(), "invariant");

ResourceMark rm;

// Retrieve information.
const Klass* const loader_klass = _cld->class_loader_klass();
const Symbol* const loader_name = _cld->name();
const Klass* const the_loader_klass = loader_klass();
const char* const the_loader_class_name = loader_class_name();
const char* const the_loader_name = loader_name();

branchtracker.print(st);

// e.g. +-- "app", jdk.internal.loader.ClassLoaders$AppClassLoader
st->print("+%.*s", BranchTracker::twig_len, "----------");
if (_cld->is_the_null_class_loader_data()) {
if (is_bootstrap()) {
st->print(" <bootstrap>");
} else {
assert(!_cld->has_class_mirror_holder(), "_cld must be the primary cld");
if (loader_name != NULL) {
st->print(" \"%s\",", loader_name->as_C_string());
if (the_loader_name[0] != '\0') {
st->print(" \"%s\",", the_loader_name);
}
st->print(" %s", loader_klass != NULL ? loader_klass->external_name() : "??");
st->print(" %s", the_loader_class_name);
if (_num_folded > 0) {
st->print(" (+ %d more)", _num_folded);
}
Expand Down Expand Up @@ -210,7 +236,7 @@ class LoaderTreeNode : public ResourceObj {
branchtracker.print(st);
st->print_cr("%*s " PTR_FORMAT, indentation, "Loader Data:", p2i(_cld));
branchtracker.print(st);
st->print_cr("%*s " PTR_FORMAT, indentation, "Loader Klass:", p2i(loader_klass));
st->print_cr("%*s " PTR_FORMAT, indentation, "Loader Klass:", p2i(the_loader_klass));

// Empty line
branchtracker.print(st);
Expand All @@ -219,6 +245,7 @@ class LoaderTreeNode : public ResourceObj {

if (print_classes) {
if (_classes != NULL) {
assert(_cld != NULL, "we have classes, we should have a CLD");
for (LoadedClassInfo* lci = _classes; lci; lci = lci->_next) {
// non-strong hidden classes should not live in
// the primary CLD of their loaders.
Expand All @@ -243,6 +270,7 @@ class LoaderTreeNode : public ResourceObj {
}

if (_hidden_classes != NULL) {
assert(_cld != NULL, "we have classes, we should have a CLD");
for (LoadedClassInfo* lci = _hidden_classes; lci; lci = lci->_next) {
branchtracker.print(st);
if (lci == _hidden_classes) { // first iteration
Expand Down Expand Up @@ -276,7 +304,7 @@ class LoaderTreeNode : public ResourceObj {
// Print children, recursively
LoaderTreeNode* c = _child;
while (c != NULL) {
c->print_with_childs(st, branchtracker, print_classes, verbose);
c->print_with_child_nodes(st, branchtracker, print_classes, verbose);
c = c->_next;
}

Expand All @@ -285,10 +313,21 @@ class LoaderTreeNode : public ResourceObj {
// Helper: Attempt to fold this node into the target node. If success, returns true.
// Folding can be done if both nodes are leaf nodes and they refer to the same loader class
// and they have the same name or no name (note: leaf check is done by caller).
bool can_fold_into(LoaderTreeNode* target_node) const {
bool can_fold_into(const LoaderTreeNode* target_node) const {
assert(is_leaf() && target_node->is_leaf(), "must be leaf");
return _cld->class_loader_klass() == target_node->_cld->class_loader_klass() &&
_cld->name() == target_node->_cld->name();

// Must have the same non-null klass
const Klass* k = loader_klass();
if (k == NULL || k != target_node->loader_klass()) {
return false;
}

// Must have the same loader name, or none
if (::strcmp(loader_name(), target_node->loader_name()) != 0) {
return false;
}

return true;
}

public:
Expand All @@ -300,6 +339,7 @@ class LoaderTreeNode : public ResourceObj {
{}

void set_cld(const ClassLoaderData* cld) {
assert(_cld == NULL, "there should be only one primary CLD per loader");
_cld = cld;
}

Expand Down Expand Up @@ -334,14 +374,6 @@ class LoaderTreeNode : public ResourceObj {
}
}

const ClassLoaderData* cld() const {
return _cld;
}

const oop loader_oop() const {
return _loader_oop;
}

LoaderTreeNode* find(const oop loader_oop) {
LoaderTreeNode* result = NULL;
if (_loader_oop == loader_oop) {
Expand All @@ -364,6 +396,7 @@ class LoaderTreeNode : public ResourceObj {
void fold_children() {
LoaderTreeNode* node = _child;
LoaderTreeNode* prev = NULL;
ResourceMark rm;
while (node != NULL) {
LoaderTreeNode* matching_node = NULL;
if (node->is_leaf()) {
Expand All @@ -389,9 +422,9 @@ class LoaderTreeNode : public ResourceObj {
}
}

void print_with_childs(outputStream* st, bool print_classes, bool print_add_info) const {
void print_with_child_nodes(outputStream* st, bool print_classes, bool print_add_info) const {
BranchTracker bwt;
print_with_childs(st, bwt, print_classes, print_add_info);
print_with_child_nodes(st, bwt, print_classes, print_add_info);
}

};
Expand Down Expand Up @@ -466,7 +499,7 @@ class LoaderInfoScanClosure : public CLDClosure {
}

void print_results(outputStream* st) const {
_root->print_with_childs(st, _print_classes, _verbose);
_root->print_with_child_nodes(st, _print_classes, _verbose);
}

void do_cld (ClassLoaderData* cld) {
Expand All @@ -483,7 +516,6 @@ class LoaderInfoScanClosure : public CLDClosure {

// Update CLD in node, but only if this is the primary CLD for this loader.
if (cld->has_class_mirror_holder() == false) {
assert(info->cld() == NULL, "there should be only one primary CLD per loader");
info->set_cld(cld);
}

Expand Down
Expand Up @@ -48,6 +48,19 @@

public class ClassLoaderHierarchyTest {

class EmptyDelegatingLoader extends ClassLoader {
EmptyDelegatingLoader(String name, ClassLoader parent) {
super(name, parent);
}
}

static void loadTestClassInLoaderAndCheck(String classname, ClassLoader loader) throws ClassNotFoundException {
Class<?> c = Class.forName(classname, true, loader);
if (c.getClassLoader() != loader) {
Assert.fail(classname + " defined by wrong classloader: " + c.getClassLoader());
}
}

//+-- <bootstrap>
// |
// +-- "platform", jdk.internal.loader.ClassLoaders$PlatformClassLoader
Expand All @@ -60,39 +73,76 @@ public class ClassLoaderHierarchyTest {

public void run(CommandExecutor executor) throws ClassNotFoundException {

// A) one unnamed, two named loaders
ClassLoader unnamed_cl = new TestClassLoader(null, null);
Class<?> c1 = Class.forName("TestClass2", true, unnamed_cl);
if (c1.getClassLoader() != unnamed_cl) {
Assert.fail("TestClass defined by wrong classloader: " + c1.getClassLoader());
}

ClassLoader named_cl = new TestClassLoader("Kevin", null);
Class<?> c2 = Class.forName("TestClass2", true, named_cl);
if (c2.getClassLoader() != named_cl) {
Assert.fail("TestClass defined by wrong classloader: " + c2.getClassLoader());
}

ClassLoader named_child_cl = new TestClassLoader("Bill", unnamed_cl);
Class<?> c3 = Class.forName("TestClass2", true, named_child_cl);
if (c3.getClassLoader() != named_child_cl) {
Assert.fail("TestClass defined by wrong classloader: " + c3.getClassLoader());
}
loadTestClassInLoaderAndCheck("TestClass2", unnamed_cl);
loadTestClassInLoaderAndCheck("TestClass2", named_cl);
loadTestClassInLoaderAndCheck("TestClass2", named_child_cl);

// B) A named CL with empty loaders as parents (JDK-8293156)
EmptyDelegatingLoader emptyLoader1 = new EmptyDelegatingLoader("EmptyLoader1", null);
EmptyDelegatingLoader emptyLoader2 = new EmptyDelegatingLoader("EmptyLoader2", emptyLoader1);
ClassLoader named_child_2_cl = new TestClassLoader("Child2", emptyLoader2);
loadTestClassInLoaderAndCheck("TestClass2", named_child_2_cl);

// C) Test output for several class loaders, same class, same name, empty parents,
// and all these should be folded by default.
EmptyDelegatingLoader emptyLoader3 = new EmptyDelegatingLoader("EmptyLoader3", null);
EmptyDelegatingLoader emptyLoader4 = new EmptyDelegatingLoader("EmptyLoader4", emptyLoader3);
ClassLoader named_child_3_cl = new TestClassLoader("ChildX", emptyLoader4); // Same names
ClassLoader named_child_4_cl = new TestClassLoader("ChildX", emptyLoader4);
ClassLoader named_child_5_cl = new TestClassLoader("ChildX", emptyLoader4);
ClassLoader named_child_6_cl = new TestClassLoader("ChildX", emptyLoader4);
loadTestClassInLoaderAndCheck("TestClass2", named_child_3_cl);
loadTestClassInLoaderAndCheck("TestClass2", named_child_4_cl);
loadTestClassInLoaderAndCheck("TestClass2", named_child_5_cl);
loadTestClassInLoaderAndCheck("TestClass2", named_child_6_cl);

// D) Test output for several *unnamed* class loaders, same class, same parents,
// and all these should be folded by default too.
EmptyDelegatingLoader emptyLoader5 = new EmptyDelegatingLoader(null, null);
EmptyDelegatingLoader emptyLoader6 = new EmptyDelegatingLoader(null, emptyLoader5);
ClassLoader named_child_7_cl = new TestClassLoader(null, emptyLoader6); // Same names
ClassLoader named_child_8_cl = new TestClassLoader(null, emptyLoader6);
ClassLoader named_child_9_cl = new TestClassLoader(null, emptyLoader6);
ClassLoader named_child_10_cl = new TestClassLoader(null, emptyLoader6);
loadTestClassInLoaderAndCheck("TestClass2", named_child_7_cl);
loadTestClassInLoaderAndCheck("TestClass2", named_child_8_cl);
loadTestClassInLoaderAndCheck("TestClass2", named_child_9_cl);
loadTestClassInLoaderAndCheck("TestClass2", named_child_10_cl);

// First test: simple output, no classes displayed
OutputAnalyzer output = executor.execute("VM.classloaders");
output.shouldContain("<bootstrap>");
output.shouldMatch(".*TestClassLoader");
output.shouldMatch("Kevin.*TestClassLoader");
output.shouldMatch("Bill.*TestClassLoader");
// (A)
output.shouldContain("+-- <bootstrap>");
output.shouldContain(" +-- \"platform\", jdk.internal.loader.ClassLoaders$PlatformClassLoader");
output.shouldContain(" | +-- \"app\", jdk.internal.loader.ClassLoaders$AppClassLoader");
output.shouldContain(" +-- \"Kevin\", ClassLoaderHierarchyTest$TestClassLoader");
output.shouldContain(" +-- ClassLoaderHierarchyTest$TestClassLoader");
output.shouldContain(" | +-- \"Bill\", ClassLoaderHierarchyTest$TestClassLoader");
// (B)
output.shouldContain(" +-- \"EmptyLoader1\", ClassLoaderHierarchyTest$EmptyDelegatingLoader");
output.shouldContain(" | +-- \"EmptyLoader2\", ClassLoaderHierarchyTest$EmptyDelegatingLoader");
output.shouldContain(" | +-- \"Child2\", ClassLoaderHierarchyTest$TestClassLoader");
// (C)
output.shouldContain(" +-- \"EmptyLoader3\", ClassLoaderHierarchyTest$EmptyDelegatingLoader");
output.shouldContain(" | +-- \"EmptyLoader4\", ClassLoaderHierarchyTest$EmptyDelegatingLoader");
output.shouldContain(" | +-- \"ChildX\", ClassLoaderHierarchyTest$TestClassLoader (+ 3 more)");
// (D)
output.shouldContain(" +-- ClassLoaderHierarchyTest$EmptyDelegatingLoader");
output.shouldContain(" +-- ClassLoaderHierarchyTest$EmptyDelegatingLoader");
output.shouldContain(" +-- ClassLoaderHierarchyTest$TestClassLoader (+ 3 more)");

// Second test: print with classes.
output = executor.execute("VM.classloaders show-classes");
output.shouldContain("<bootstrap>");
output.shouldContain("java.lang.Object");
output.shouldMatch(".*TestClassLoader");
output.shouldMatch("Kevin.*TestClassLoader");
output.shouldMatch("Bill.*TestClassLoader");
output.shouldContain("java.lang.Enum");
output.shouldContain("java.lang.NullPointerException");
output.shouldContain("TestClass2");

output.shouldContain("Hidden Classes:");

Reference.reachabilityFence(unnamed_cl);
Expand Down

0 comments on commit c6be2cd

Please sign in to comment.