812 lines
29 KiB
Diff
812 lines
29 KiB
Diff
From 953fdbbfbc6512c3f04f3663fa5ad216d7547984 Mon Sep 17 00:00:00 2001
|
|
From: eapen <zhangyipeng7@huawei.com>
|
|
Date: Thu, 15 Dec 2022 09:48:37 +0800
|
|
Subject: [PATCH 17/33] I68TO2: 8203682: Add jcmd "VM.classloaders" command to print
|
|
out class loader hierarchy, details
|
|
---
|
|
.../vm/classfile/classLoaderHierarchyDCmd.cpp | 468 +++++++++++++++++++++
|
|
.../vm/classfile/classLoaderHierarchyDCmd.hpp | 59 +++
|
|
hotspot/src/share/vm/runtime/vm_operations.hpp | 1 +
|
|
.../src/share/vm/services/diagnosticCommand.cpp | 2 +
|
|
.../dcmd/ClassLoaderHierarchyTest.java | 213 ++++++++++
|
|
5 files changed, 743 insertions(+)
|
|
create mode 100644 hotspot/src/share/vm/classfile/classLoaderHierarchyDCmd.cpp
|
|
create mode 100644 hotspot/src/share/vm/classfile/classLoaderHierarchyDCmd.hpp
|
|
create mode 100644 hotspot/test/serviceability/dcmd/ClassLoaderHierarchyTest.java
|
|
|
|
diff --git a/hotspot/src/share/vm/classfile/classLoaderHierarchyDCmd.cpp b/hotspot/src/share/vm/classfile/classLoaderHierarchyDCmd.cpp
|
|
new file mode 100644
|
|
index 0000000..4c25091
|
|
--- /dev/null
|
|
+++ b/hotspot/src/share/vm/classfile/classLoaderHierarchyDCmd.cpp
|
|
@@ -0,0 +1,468 @@
|
|
+/*
|
|
+ * Copyright (c) 2021, Huawei Technologies Co., Ltd. All rights reserved.
|
|
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
|
+ * Copyright (c) 2018 SAP SE. 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 "classfile/classLoaderData.inline.hpp"
|
|
+#include "classfile/classLoaderHierarchyDCmd.hpp"
|
|
+#include "memory/allocation.hpp"
|
|
+#include "memory/resourceArea.hpp"
|
|
+#include "runtime/safepoint.hpp"
|
|
+#include "utilities/globalDefinitions.hpp"
|
|
+#include "utilities/ostream.hpp"
|
|
+
|
|
+
|
|
+ClassLoaderHierarchyDCmd::ClassLoaderHierarchyDCmd(outputStream* output, bool heap)
|
|
+ : DCmdWithParser(output, heap)
|
|
+ , _show_classes("show-classes", "Print loaded classes.", "BOOLEAN", false, "false")
|
|
+ , _verbose("verbose", "Print detailed information.", "BOOLEAN", false, "false") {
|
|
+ _dcmdparser.add_dcmd_option(&_show_classes);
|
|
+ _dcmdparser.add_dcmd_option(&_verbose);
|
|
+}
|
|
+
|
|
+
|
|
+int ClassLoaderHierarchyDCmd::num_arguments() {
|
|
+ ResourceMark rm;
|
|
+ ClassLoaderHierarchyDCmd* dcmd = new ClassLoaderHierarchyDCmd(NULL, false);
|
|
+ if (dcmd != NULL) {
|
|
+ DCmdMark mark(dcmd);
|
|
+ return dcmd->_dcmdparser.num_arguments();
|
|
+ } else {
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+// Helper class for drawing the branches to the left of a node.
|
|
+class BranchTracker : public StackObj {
|
|
+ // "<x>"
|
|
+ // " |---<y>"
|
|
+ // " | |
|
|
+ // " | <z>"
|
|
+ // " | |---<z1>
|
|
+ // " | |---<z2>
|
|
+ // ^^^^^^^ ^^^
|
|
+ // A B
|
|
+
|
|
+ // Some terms for the graphics:
|
|
+ // - branch: vertical connection between a node's ancestor to a later sibling.
|
|
+ // - branchwork: (A) the string to print as a prefix at the start of each line, contains all branches.
|
|
+ // - twig (B): Length of the dashed line connecting a node to its branch.
|
|
+ // - branch spacing: how many spaces between branches are printed.
|
|
+
|
|
+public:
|
|
+
|
|
+ enum { max_depth = 64, twig_len = 2, branch_spacing = 5 };
|
|
+
|
|
+private:
|
|
+
|
|
+ char _branches[max_depth];
|
|
+ int _pos;
|
|
+
|
|
+public:
|
|
+ BranchTracker()
|
|
+ : _pos(0) {}
|
|
+
|
|
+ void push(bool has_branch) {
|
|
+ if (_pos < max_depth) {
|
|
+ _branches[_pos] = has_branch ? '|' : ' ';
|
|
+ }
|
|
+ _pos ++; // beyond max depth, omit branch drawing but do count on.
|
|
+ }
|
|
+
|
|
+ void pop() {
|
|
+ assert(_pos > 0, "must be");
|
|
+ _pos --;
|
|
+ }
|
|
+
|
|
+ void print(outputStream* st) {
|
|
+ for (int i = 0; i < _pos; i ++) {
|
|
+ st->print("%c%.*s", _branches[i], branch_spacing, " ");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ class Mark {
|
|
+ BranchTracker& _tr;
|
|
+ public:
|
|
+ Mark(BranchTracker& tr, bool has_branch_here)
|
|
+ : _tr(tr) { _tr.push(has_branch_here); }
|
|
+ ~Mark() { _tr.pop(); }
|
|
+ };
|
|
+
|
|
+}; // end: BranchTracker
|
|
+
|
|
+struct LoadedClassInfo : public ResourceObj {
|
|
+public:
|
|
+ LoadedClassInfo* _next;
|
|
+ Klass* const _klass;
|
|
+ const ClassLoaderData* const _cld;
|
|
+
|
|
+ LoadedClassInfo(Klass* klass, const ClassLoaderData* cld)
|
|
+ : _next(NULL), _klass(klass), _cld(cld) {}
|
|
+
|
|
+};
|
|
+
|
|
+class LoaderTreeNode : public ResourceObj {
|
|
+
|
|
+ // We walk the CLDG and, for each CLD which is non-anonymous, add
|
|
+ // a tree node. To add a node we need its parent node; if it itself
|
|
+ // does not exist yet, we add a preliminary node for it. This preliminary
|
|
+ // node just contains its loader oop; later, when encountering its CLD in
|
|
+ // our CLDG walk, we complete the missing information in this node.
|
|
+
|
|
+ const oop _loader_oop;
|
|
+ const ClassLoaderData* _cld; // May be NULL if loader never loaded anything
|
|
+
|
|
+ LoaderTreeNode* _child;
|
|
+ LoaderTreeNode* _next;
|
|
+
|
|
+ LoadedClassInfo* _classes;
|
|
+ int _num_classes;
|
|
+
|
|
+ LoadedClassInfo* _anon_classes;
|
|
+ int _num_anon_classes;
|
|
+
|
|
+ // Returns Klass of loader; NULL for bootstrap loader
|
|
+ const Klass* loader_klass() const {
|
|
+ return (_loader_oop != NULL) ? _loader_oop->klass() : NULL;
|
|
+ }
|
|
+
|
|
+ // 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() : "";
|
|
+ }
|
|
+
|
|
+ bool is_bootstrap() const {
|
|
+ if (_loader_oop == NULL) {
|
|
+ assert(_cld != NULL && _cld->is_the_null_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 the_loader_klass = loader_klass();
|
|
+ const char* const the_loader_class_name = loader_class_name();
|
|
+ // ClassLoader.java does not contain 'name' field. Replace it with loader_class_name().
|
|
+ const char* const the_loader_name = the_loader_class_name;
|
|
+
|
|
+ branchtracker.print(st);
|
|
+
|
|
+ // e.g. "+--- jdk.internal.reflect.DelegatingClassLoader"
|
|
+ st->print("+%.*s", BranchTracker::twig_len, "----------");
|
|
+ if (is_bootstrap()) {
|
|
+ st->print(" <bootstrap>");
|
|
+ } else {
|
|
+ if (the_loader_name[0] != '\0') {
|
|
+ st->print(" \"%s\",", the_loader_name);
|
|
+ }
|
|
+ st->print(" %s", the_loader_class_name);
|
|
+ st->print(" {" PTR_FORMAT "}", p2i(_loader_oop));
|
|
+ }
|
|
+ st->cr();
|
|
+
|
|
+ // Output following this node (node details and child nodes) - up to the next sibling node
|
|
+ // needs to be prefixed with "|" if there is a follow up sibling.
|
|
+ const bool have_sibling = _next != NULL;
|
|
+ BranchTracker::Mark trm(branchtracker, have_sibling);
|
|
+
|
|
+ {
|
|
+ // optional node details following this node needs to be prefixed with "|"
|
|
+ // if there are follow up child nodes.
|
|
+ const bool have_child = _child != NULL;
|
|
+ BranchTracker::Mark trm(branchtracker, have_child);
|
|
+
|
|
+ // Empty line
|
|
+ branchtracker.print(st);
|
|
+ st->cr();
|
|
+
|
|
+ const int indentation = 18;
|
|
+
|
|
+ if (verbose) {
|
|
+ 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(the_loader_klass));
|
|
+
|
|
+ // Empty line
|
|
+ branchtracker.print(st);
|
|
+ st->cr();
|
|
+ }
|
|
+
|
|
+ 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) {
|
|
+ branchtracker.print(st);
|
|
+ if (lci == _classes) { // first iteration
|
|
+ st->print("%*s ", indentation, "Classes:");
|
|
+ } else {
|
|
+ st->print("%*s ", indentation, "");
|
|
+ }
|
|
+ st->print("%s", lci->_klass->external_name());
|
|
+ st->cr();
|
|
+ // Non-anonymous classes should live in the primary CLD of its loader
|
|
+ assert(lci->_cld == _cld, "must be");
|
|
+ }
|
|
+ branchtracker.print(st);
|
|
+ st->print("%*s ", indentation, "");
|
|
+ st->print_cr("(%u class%s)", _num_classes, (_num_classes == 1) ? "" : "es");
|
|
+
|
|
+ // Empty line
|
|
+ branchtracker.print(st);
|
|
+ st->cr();
|
|
+ }
|
|
+
|
|
+ if (_anon_classes != NULL) {
|
|
+ assert(_cld != NULL, "we have classes, we should have a CLD");
|
|
+ for (LoadedClassInfo* lci = _anon_classes; lci; lci = lci->_next) {
|
|
+ branchtracker.print(st);
|
|
+ if (lci == _anon_classes) { // first iteration
|
|
+ st->print("%*s ", indentation, "Anonymous Classes:");
|
|
+ } else {
|
|
+ st->print("%*s ", indentation, "");
|
|
+ }
|
|
+ st->print("%s", lci->_klass->external_name());
|
|
+ // For anonymous classes, also print CLD if verbose. Should be a different one than the primary CLD.
|
|
+ assert(lci->_cld != _cld, "must be");
|
|
+ if (verbose) {
|
|
+ st->print(" (CLD: " PTR_FORMAT ")", p2i(lci->_cld));
|
|
+ }
|
|
+ st->cr();
|
|
+ }
|
|
+ branchtracker.print(st);
|
|
+ st->print("%*s ", indentation, "");
|
|
+ st->print_cr("(%u anonymous class%s)", _num_anon_classes, (_num_anon_classes == 1) ? "" : "es");
|
|
+
|
|
+ // Empty line
|
|
+ branchtracker.print(st);
|
|
+ st->cr();
|
|
+ }
|
|
+
|
|
+ } // end: print_classes
|
|
+
|
|
+ } // Pop branchtracker mark
|
|
+
|
|
+ // Print children, recursively
|
|
+ LoaderTreeNode* c = _child;
|
|
+ while (c != NULL) {
|
|
+ c->print_with_child_nodes(st, branchtracker, print_classes, verbose);
|
|
+ c = c->_next;
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+public:
|
|
+
|
|
+ LoaderTreeNode(const oop loader_oop)
|
|
+ : _loader_oop(loader_oop), _cld(NULL)
|
|
+ , _child(NULL), _next(NULL)
|
|
+ , _classes(NULL), _anon_classes(NULL)
|
|
+ , _num_classes(0), _num_anon_classes(0) {}
|
|
+
|
|
+ void set_cld(const ClassLoaderData* cld) {
|
|
+ assert(_cld == NULL, "there should be only one primary CLD per loader");
|
|
+ _cld = cld;
|
|
+ }
|
|
+
|
|
+ void add_child(LoaderTreeNode* info) {
|
|
+ info->_next = _child;
|
|
+ _child = info;
|
|
+ }
|
|
+
|
|
+ void add_sibling(LoaderTreeNode* info) {
|
|
+ assert(info->_next == NULL, "must be");
|
|
+ info->_next = _next;
|
|
+ _next = info;
|
|
+ }
|
|
+
|
|
+ void add_classes(LoadedClassInfo* first_class, int num_classes, bool anonymous) {
|
|
+ LoadedClassInfo** p_list_to_add_to = anonymous ? &_anon_classes : &_classes;
|
|
+ // Search tail.
|
|
+ while ((*p_list_to_add_to) != NULL) {
|
|
+ p_list_to_add_to = &(*p_list_to_add_to)->_next;
|
|
+ }
|
|
+ *p_list_to_add_to = first_class;
|
|
+ if (anonymous) {
|
|
+ _num_anon_classes += num_classes;
|
|
+ } else {
|
|
+ _num_classes += num_classes;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ LoaderTreeNode* find(const oop loader_oop) {
|
|
+ LoaderTreeNode* result = NULL;
|
|
+ if (_loader_oop == loader_oop) {
|
|
+ result = this;
|
|
+ } else {
|
|
+ LoaderTreeNode* c = _child;
|
|
+ while (c != NULL && result == NULL) {
|
|
+ result = c->find(loader_oop);
|
|
+ c = c->_next;
|
|
+ }
|
|
+ }
|
|
+ return result;
|
|
+ }
|
|
+
|
|
+ void print_with_child_nodes(outputStream* st, bool print_classes, bool print_add_info) const {
|
|
+ BranchTracker bwt;
|
|
+ print_with_child_nodes(st, bwt, print_classes, print_add_info);
|
|
+ }
|
|
+
|
|
+};
|
|
+
|
|
+class LoadedClassCollectClosure : public KlassClosure {
|
|
+public:
|
|
+ LoadedClassInfo* _list;
|
|
+ const ClassLoaderData* _cld;
|
|
+ int _num_classes;
|
|
+ LoadedClassCollectClosure(const ClassLoaderData* cld)
|
|
+ : _list(NULL), _cld(cld), _num_classes(0) {}
|
|
+ void do_klass(Klass* k) {
|
|
+ LoadedClassInfo* lki = new LoadedClassInfo(k, _cld);
|
|
+ lki->_next = _list;
|
|
+ _list = lki;
|
|
+ _num_classes ++;
|
|
+ }
|
|
+};
|
|
+
|
|
+class LoaderInfoScanClosure : public CLDClosure {
|
|
+
|
|
+ const bool _print_classes;
|
|
+ const bool _verbose;
|
|
+ LoaderTreeNode* _root;
|
|
+
|
|
+ static void fill_in_classes(LoaderTreeNode* info, const ClassLoaderData* cld) {
|
|
+ assert(info != NULL && cld != NULL, "must be");
|
|
+ LoadedClassCollectClosure lccc(cld);
|
|
+ const_cast<ClassLoaderData*>(cld)->classes_do(&lccc);
|
|
+ if (lccc._num_classes > 0) {
|
|
+ info->add_classes(lccc._list, lccc._num_classes, cld->is_anonymous());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ LoaderTreeNode* find_node_or_add_empty_node(oop loader_oop) {
|
|
+
|
|
+ assert(_root != NULL, "root node must exist");
|
|
+
|
|
+ if (loader_oop == NULL) {
|
|
+ return _root;
|
|
+ }
|
|
+
|
|
+ // Check if a node for this oop already exists.
|
|
+ LoaderTreeNode* info = _root->find(loader_oop);
|
|
+
|
|
+ if (info == NULL) {
|
|
+ // It does not. Create a node.
|
|
+ info = new LoaderTreeNode(loader_oop);
|
|
+
|
|
+ // Add it to tree.
|
|
+ LoaderTreeNode* parent_info = NULL;
|
|
+
|
|
+ // Recursively add parent nodes if needed.
|
|
+ const oop parent_oop = java_lang_ClassLoader::parent(loader_oop);
|
|
+ if (parent_oop == NULL) {
|
|
+ parent_info = _root;
|
|
+ } else {
|
|
+ parent_info = find_node_or_add_empty_node(parent_oop);
|
|
+ }
|
|
+ assert(parent_info != NULL, "must be");
|
|
+
|
|
+ parent_info->add_child(info);
|
|
+ }
|
|
+ return info;
|
|
+ }
|
|
+
|
|
+
|
|
+public:
|
|
+ LoaderInfoScanClosure(bool print_classes, bool verbose)
|
|
+ : _print_classes(print_classes), _verbose(verbose), _root(NULL) {
|
|
+ _root = new LoaderTreeNode(NULL);
|
|
+ }
|
|
+
|
|
+ void print_results(outputStream* st) const {
|
|
+ _root->print_with_child_nodes(st, _print_classes, _verbose);
|
|
+ }
|
|
+
|
|
+ void do_cld (ClassLoaderData* cld) {
|
|
+
|
|
+ // We do not display unloading loaders, for now.
|
|
+ if (cld->is_unloading()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ const oop loader_oop = cld->class_loader();
|
|
+
|
|
+ LoaderTreeNode* info = find_node_or_add_empty_node(loader_oop);
|
|
+ assert(info != NULL, "must be");
|
|
+
|
|
+ // Update CLD in node, but only if this is the primary CLD for this loader.
|
|
+ if (cld->is_anonymous() == false) {
|
|
+ info->set_cld(cld);
|
|
+ }
|
|
+
|
|
+ // Add classes.
|
|
+ fill_in_classes(info, cld);
|
|
+ }
|
|
+
|
|
+};
|
|
+
|
|
+
|
|
+class ClassLoaderHierarchyVMOperation : public VM_Operation {
|
|
+ outputStream* const _out;
|
|
+ const bool _show_classes;
|
|
+ const bool _verbose;
|
|
+public:
|
|
+ ClassLoaderHierarchyVMOperation(outputStream* out, bool show_classes, bool verbose) :
|
|
+ _out(out), _show_classes(show_classes), _verbose(verbose)
|
|
+ {}
|
|
+
|
|
+ VMOp_Type type() const {
|
|
+ return VMOp_ClassLoaderHierarchyOperation;
|
|
+ }
|
|
+
|
|
+ void doit() {
|
|
+ assert(SafepointSynchronize::is_at_safepoint(), "must be a safepoint");
|
|
+ ResourceMark rm;
|
|
+ LoaderInfoScanClosure cl (_show_classes, _verbose);
|
|
+ ClassLoaderDataGraph::cld_do(&cl);
|
|
+ cl.print_results(_out);
|
|
+ }
|
|
+};
|
|
+
|
|
+// This command needs to be executed at a safepoint.
|
|
+void ClassLoaderHierarchyDCmd::execute(DCmdSource source, TRAPS) {
|
|
+ ClassLoaderHierarchyVMOperation op(output(), _show_classes.value(), _verbose.value());
|
|
+ VMThread::execute(&op);
|
|
+}
|
|
\ No newline at end of file
|
|
diff --git a/hotspot/src/share/vm/classfile/classLoaderHierarchyDCmd.hpp b/hotspot/src/share/vm/classfile/classLoaderHierarchyDCmd.hpp
|
|
new file mode 100644
|
|
index 0000000..49027e6
|
|
--- /dev/null
|
|
+++ b/hotspot/src/share/vm/classfile/classLoaderHierarchyDCmd.hpp
|
|
@@ -0,0 +1,59 @@
|
|
+/*
|
|
+ * Copyright (c) 2021, Huawei Technologies Co., Ltd. All rights reserved.
|
|
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
|
+ * Copyright (c) 2018 SAP SE. 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 HOTSPOT_SHARE_CLASSFILE_CLASSLOADERHIERARCHYDCMD_HPP_
|
|
+#define HOTSPOT_SHARE_CLASSFILE_CLASSLOADERHIERARCHYDCMD_HPP_
|
|
+
|
|
+#include "services/diagnosticCommand.hpp"
|
|
+
|
|
+class ClassLoaderHierarchyDCmd: public DCmdWithParser {
|
|
+ DCmdArgument<bool> _show_classes;
|
|
+ DCmdArgument<bool> _verbose;
|
|
+public:
|
|
+
|
|
+ ClassLoaderHierarchyDCmd(outputStream* output, bool heap);
|
|
+
|
|
+ static const char* name() {
|
|
+ return "VM.classloaders";
|
|
+ }
|
|
+
|
|
+ static const char* description() {
|
|
+ return "Prints classloader hierarchy.";
|
|
+ }
|
|
+ static const char* impact() {
|
|
+ return "Medium: Depends on number of class loaders and classes loaded.";
|
|
+ }
|
|
+ static const JavaPermission permission() {
|
|
+ JavaPermission p = {"java.lang.management.ManagementPermission",
|
|
+ "monitor", NULL};
|
|
+ return p;
|
|
+ }
|
|
+ static int num_arguments();
|
|
+ virtual void execute(DCmdSource source, TRAPS);
|
|
+
|
|
+};
|
|
+
|
|
+#endif /* HOTSPOT_SHARE_CLASSFILE_CLASSLOADERHIERARCHYDCMD_HPP_ */
|
|
\ No newline at end of file
|
|
diff --git a/hotspot/src/share/vm/runtime/vm_operations.hpp b/hotspot/src/share/vm/runtime/vm_operations.hpp
|
|
index a8ba78b..3744040 100644
|
|
--- a/hotspot/src/share/vm/runtime/vm_operations.hpp
|
|
+++ b/hotspot/src/share/vm/runtime/vm_operations.hpp
|
|
@@ -98,6 +98,7 @@
|
|
template(RotateGCLog) \
|
|
template(WhiteBoxOperation) \
|
|
template(ClassLoaderStatsOperation) \
|
|
+ template(ClassLoaderHierarchyOperation) \
|
|
template(JFROldObject) \
|
|
template(PrintClasses) \
|
|
|
|
diff --git a/hotspot/src/share/vm/services/diagnosticCommand.cpp b/hotspot/src/share/vm/services/diagnosticCommand.cpp
|
|
index e4e6185..d3b91d9 100644
|
|
--- a/hotspot/src/share/vm/services/diagnosticCommand.cpp
|
|
+++ b/hotspot/src/share/vm/services/diagnosticCommand.cpp
|
|
@@ -24,6 +24,7 @@
|
|
|
|
#include "precompiled.hpp"
|
|
#include "cds/dynamicArchive.hpp"
|
|
+#include "classfile/classLoaderHierarchyDCmd.hpp"
|
|
#include "classfile/classLoaderStats.hpp"
|
|
#include "gc_implementation/shared/vmGCOperations.hpp"
|
|
#include "runtime/javaCalls.hpp"
|
|
@@ -70,6 +71,7 @@ void DCmdRegistrant::register_dcmds(){
|
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpDCmd>(full_export, true, false));
|
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<RotateGCLogDCmd>(full_export, true, false));
|
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderStatsDCmd>(full_export, true, false));
|
|
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderHierarchyDCmd>(full_export, true, false));
|
|
#ifdef LINUX
|
|
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<TrimCLibcHeapDCmd>(full_export, true, false));
|
|
#endif // LINUX
|
|
diff --git a/hotspot/test/serviceability/dcmd/ClassLoaderHierarchyTest.java b/hotspot/test/serviceability/dcmd/ClassLoaderHierarchyTest.java
|
|
new file mode 100644
|
|
index 0000000..378997d
|
|
--- /dev/null
|
|
+++ b/hotspot/test/serviceability/dcmd/ClassLoaderHierarchyTest.java
|
|
@@ -0,0 +1,213 @@
|
|
+/*
|
|
+ * Copyright (c) 2021, Huawei Technologies Co., Ltd. All rights reserved.
|
|
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
|
|
+ * Copyright (c) 2018, SAP SE. 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 of diagnostic command VM.classloaders
|
|
+ * @library /testlibrary
|
|
+ * @modules java.base/jdk.internal.misc
|
|
+ * java.compiler
|
|
+ * java.management
|
|
+ * jdk.internal.jvmstat/sun.jvmstat.monitor
|
|
+ * @run testng ClassLoaderHierarchyTest
|
|
+ */
|
|
+
|
|
+import org.testng.Assert;
|
|
+import org.testng.annotations.Test;
|
|
+
|
|
+import com.oracle.java.testlibrary.OutputAnalyzer;
|
|
+import com.oracle.java.testlibrary.CommandExecutor;
|
|
+import com.oracle.java.testlibrary.JMXExecutor;
|
|
+
|
|
+import java.io.File;
|
|
+import java.io.FileInputStream;
|
|
+import java.io.IOException;
|
|
+import java.nio.ByteBuffer;
|
|
+import java.nio.channels.FileChannel;
|
|
+
|
|
+public class ClassLoaderHierarchyTest {
|
|
+
|
|
+ class EmptyDelegatingLoader extends ClassLoader {
|
|
+ EmptyDelegatingLoader(ClassLoader parent) {
|
|
+ super(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>
|
|
+// |
|
|
+// +-- "sun.misc.Launcher$ExtClassLoader", sun.misc.Launcher$ExtClassLoader
|
|
+// | |
|
|
+// | +-- "sun.misc.Launcher$AppClassLoader", sun.misc.Launcher$AppClassLoader
|
|
+// |
|
|
+// +-- "sun.reflect.DelegatingClassLoader", sun.reflect.DelegatingClassLoader
|
|
+// |
|
|
+// +-- "ClassLoaderHierarchyTest$TestClassLoader", ClassLoaderHierarchyTest$TestClassLoader
|
|
+// | |
|
|
+// | +-- "ClassLoaderHierarchyTest$TestClassLoader", ClassLoaderHierarchyTest$TestClassLoader
|
|
+// |
|
|
+// +-- "ClassLoaderHierarchyTest$EmptyDelegatingLoader", ClassLoaderHierarchyTest$EmptyDelegatingLoader
|
|
+// | |
|
|
+// | +-- "ClassLoaderHierarchyTest$EmptyDelegatingLoader", ClassLoaderHierarchyTest$EmptyDelegatingLoader
|
|
+// | |
|
|
+// | +-- "ClassLoaderHierarchyTest$TestClassLoader", ClassLoaderHierarchyTest$TestClassLoader
|
|
+// |
|
|
+// +-- "ClassLoaderHierarchyTest$EmptyDelegatingLoader", ClassLoaderHierarchyTest$EmptyDelegatingLoader
|
|
+// |
|
|
+// +-- "ClassLoaderHierarchyTest$EmptyDelegatingLoader", ClassLoaderHierarchyTest$EmptyDelegatingLoader
|
|
+// |
|
|
+// +-- "ClassLoaderHierarchyTest$TestClassLoader", ClassLoaderHierarchyTest$TestClassLoader
|
|
+// |
|
|
+// +-- "ClassLoaderHierarchyTest$TestClassLoader", ClassLoaderHierarchyTest$TestClassLoader
|
|
+// |
|
|
+// +-- "ClassLoaderHierarchyTest$TestClassLoader", ClassLoaderHierarchyTest$TestClassLoader
|
|
+
|
|
+
|
|
+ public void run(CommandExecutor executor) throws ClassNotFoundException {
|
|
+
|
|
+ // A) one unnamed, two named loaders
|
|
+ ClassLoader unnamed_cl = new TestClassLoader(null);
|
|
+ ClassLoader named_child_cl = new TestClassLoader(unnamed_cl);
|
|
+ loadTestClassInLoaderAndCheck("TestClass2", unnamed_cl);
|
|
+ loadTestClassInLoaderAndCheck("TestClass2", named_child_cl);
|
|
+
|
|
+ // B) A named CL with empty loaders as parents (JDK-8293156)
|
|
+ EmptyDelegatingLoader emptyLoader1 = new EmptyDelegatingLoader( null);
|
|
+ EmptyDelegatingLoader emptyLoader2 = new EmptyDelegatingLoader(emptyLoader1);
|
|
+ ClassLoader named_child_2_cl = new TestClassLoader(emptyLoader2);
|
|
+ loadTestClassInLoaderAndCheck("TestClass2", named_child_2_cl);
|
|
+
|
|
+ // C) Test output for several *unnamed* class loaders, same class, same parents,
|
|
+ // and all these should be folded by default.
|
|
+ EmptyDelegatingLoader emptyLoader3 = new EmptyDelegatingLoader(null);
|
|
+ EmptyDelegatingLoader emptyLoader4 = new EmptyDelegatingLoader(emptyLoader3);
|
|
+ ClassLoader named_child_3_cl = new TestClassLoader(emptyLoader4); // Same names
|
|
+ ClassLoader named_child_4_cl = new TestClassLoader(emptyLoader4);
|
|
+ ClassLoader named_child_5_cl = new TestClassLoader(emptyLoader4);
|
|
+ loadTestClassInLoaderAndCheck("TestClass2", named_child_3_cl);
|
|
+ loadTestClassInLoaderAndCheck("TestClass2", named_child_4_cl);
|
|
+ loadTestClassInLoaderAndCheck("TestClass2", named_child_5_cl);
|
|
+
|
|
+ // First test: simple output, no classes displayed
|
|
+ OutputAnalyzer output = executor.execute("VM.classloaders");
|
|
+ // (A)
|
|
+ output.shouldContain("+-- <bootstrap>");
|
|
+ output.shouldContain(" +-- \"sun.misc.Launcher$ExtClassLoader\", sun.misc.Launcher$ExtClassLoader");
|
|
+ output.shouldContain(" | +-- \"sun.misc.Launcher$AppClassLoader\", sun.misc.Launcher$AppClassLoader");
|
|
+ output.shouldContain(" +-- \"sun.reflect.DelegatingClassLoader\", sun.reflect.DelegatingClassLoader");
|
|
+ output.shouldContain(" +-- \"ClassLoaderHierarchyTest$TestClassLoader\", ClassLoaderHierarchyTest$TestClassLoader");
|
|
+ output.shouldContain(" | +-- \"ClassLoaderHierarchyTest$TestClassLoader\", ClassLoaderHierarchyTest$TestClassLoader");
|
|
+ // (B)
|
|
+ output.shouldContain(" +-- \"ClassLoaderHierarchyTest$EmptyDelegatingLoader\", ClassLoaderHierarchyTest$EmptyDelegatingLoader");
|
|
+ output.shouldContain(" | +-- \"ClassLoaderHierarchyTest$EmptyDelegatingLoader\", ClassLoaderHierarchyTest$EmptyDelegatingLoader");
|
|
+ output.shouldContain(" | +-- \"ClassLoaderHierarchyTest$TestClassLoader\", ClassLoaderHierarchyTest$TestClassLoader");
|
|
+ // (C)
|
|
+ output.shouldContain(" +-- \"ClassLoaderHierarchyTest$EmptyDelegatingLoader\", ClassLoaderHierarchyTest$EmptyDelegatingLoader");
|
|
+ output.shouldContain(" +-- \"ClassLoaderHierarchyTest$EmptyDelegatingLoader\", ClassLoaderHierarchyTest$EmptyDelegatingLoader");
|
|
+ output.shouldContain(" +-- \"ClassLoaderHierarchyTest$TestClassLoader\", ClassLoaderHierarchyTest$TestClassLoader");
|
|
+
|
|
+ // Second test: print with classes.
|
|
+ output = executor.execute("VM.classloaders show-classes");
|
|
+ output.shouldContain("<bootstrap>");
|
|
+ output.shouldContain("java.lang.Object");
|
|
+ output.shouldContain("java.lang.Enum");
|
|
+ output.shouldContain("java.lang.NullPointerException");
|
|
+ output.shouldContain("TestClass2");
|
|
+ }
|
|
+
|
|
+ static class TestClassLoader extends ClassLoader {
|
|
+
|
|
+ public TestClassLoader() {
|
|
+ super();
|
|
+ }
|
|
+
|
|
+ public TestClassLoader(ClassLoader parent) {
|
|
+ super(parent);
|
|
+ }
|
|
+
|
|
+ public static final String CLASS_NAME = "TestClass2";
|
|
+
|
|
+ static ByteBuffer readClassFile(String name)
|
|
+ {
|
|
+ File f = new File(System.getProperty("test.classes", "."),
|
|
+ name);
|
|
+ try (FileInputStream fin = new FileInputStream(f);
|
|
+ FileChannel fc = fin.getChannel())
|
|
+ {
|
|
+ return fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
|
|
+ } catch (IOException e) {
|
|
+ Assert.fail("Can't open file: " + name, e);
|
|
+ }
|
|
+
|
|
+ /* Will not reach here as Assert.fail() throws exception */
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ protected Class<?> loadClass(String name, boolean resolve)
|
|
+ throws ClassNotFoundException
|
|
+ {
|
|
+ Class<?> c;
|
|
+ if (!CLASS_NAME.equals(name)) {
|
|
+ c = super.loadClass(name, resolve);
|
|
+ } else {
|
|
+ // should not delegate to the system class loader
|
|
+ c = findClass(name);
|
|
+ if (resolve) {
|
|
+ resolveClass(c);
|
|
+ }
|
|
+ }
|
|
+ return c;
|
|
+ }
|
|
+
|
|
+ protected Class<?> findClass(String name)
|
|
+ throws ClassNotFoundException
|
|
+ {
|
|
+ if (!CLASS_NAME.equals(name)) {
|
|
+ throw new ClassNotFoundException("Unexpected class: " + name);
|
|
+ }
|
|
+ return defineClass(name, readClassFile(name + ".class"), null);
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void jmx() throws ClassNotFoundException {
|
|
+ run(new JMXExecutor());
|
|
+ }
|
|
+
|
|
+}
|
|
+
|
|
+class TestClass2 {
|
|
+ static {
|
|
+ Runnable r = () -> System.out.println("Hello");
|
|
+ r.run();
|
|
+ }
|
|
+}
|
|
\ No newline at end of file
|
|
--
|
|
1.8.3.1
|