8054888: Runtime: Add Diagnostic Command that prints the class hierarchy
Summary: Added the VM.class_hierarhcy DCMD
Reviewed-by: stefank, miauno
--- a/hotspot/src/share/vm/memory/heapInspection.cpp Fri Feb 13 13:17:13 2015 +0100
+++ b/hotspot/src/share/vm/memory/heapInspection.cpp Wed Feb 11 15:22:43 2015 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,7 @@
#include "precompiled.hpp"
#include "classfile/classLoaderData.hpp"
+#include "classfile/systemDictionary.hpp"
#include "gc_interface/collectedHeap.hpp"
#include "memory/genCollectedHeap.hpp"
#include "memory/heapInspection.hpp"
@@ -39,6 +40,19 @@
// HeapInspection
+inline KlassInfoEntry::~KlassInfoEntry() {
+ if (_subclasses != NULL) {
+ delete _subclasses;
+ }
+}
+
+inline void KlassInfoEntry::add_subclass(KlassInfoEntry* cie) {
+ if (_subclasses == NULL) {
+ _subclasses = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<KlassInfoEntry*>(4, true);
+ }
+ _subclasses->append(cie);
+}
+
int KlassInfoEntry::compare(KlassInfoEntry* e1, KlassInfoEntry* e2) {
if(e1->_instance_words > e2->_instance_words) {
return -1;
@@ -129,7 +143,7 @@
_table->lookup(k);
}
-KlassInfoTable::KlassInfoTable(bool need_class_stats) {
+KlassInfoTable::KlassInfoTable(bool add_all_classes) {
_size_of_instances_in_words = 0;
_size = 0;
_ref = (HeapWord*) Universe::boolArrayKlassObj();
@@ -141,7 +155,7 @@
for (int index = 0; index < _size; index++) {
_buckets[index].initialize();
}
- if (need_class_stats) {
+ if (add_all_classes) {
AllClassesFinder finder(this);
ClassLoaderDataGraph::classes_do(&finder);
}
@@ -299,6 +313,191 @@
st->cr();
}
+class HierarchyClosure : public KlassInfoClosure {
+private:
+ GrowableArray<KlassInfoEntry*> *_elements;
+public:
+ HierarchyClosure(GrowableArray<KlassInfoEntry*> *_elements) : _elements(_elements) {}
+
+ void do_cinfo(KlassInfoEntry* cie) {
+ // ignore array classes
+ if (cie->klass()->oop_is_instance()) {
+ _elements->append(cie);
+ }
+ }
+};
+
+void KlassHierarchy::print_class_hierarchy(outputStream* st, bool print_interfaces,
+ bool print_subclasses, char* classname) {
+ ResourceMark rm;
+ Stack <KlassInfoEntry*, mtClass> class_stack;
+ GrowableArray<KlassInfoEntry*> elements;
+
+ // Add all classes to the KlassInfoTable, which allows for quick lookup.
+ // A KlassInfoEntry will be created for each class.
+ KlassInfoTable cit(true);
+ if (cit.allocation_failed()) {
+ st->print_cr("ERROR: Ran out of C-heap; hierarchy not generated");
+ return;
+ }
+
+ // Add all created KlassInfoEntry instances to the elements array for easy
+ // iteration, and to allow each KlassInfoEntry instance to have a unique index.
+ HierarchyClosure hc(&elements);
+ cit.iterate(&hc);
+
+ for(int i = 0; i < elements.length(); i++) {
+ KlassInfoEntry* cie = elements.at(i);
+ const InstanceKlass* k = (InstanceKlass*)cie->klass();
+ Klass* super = ((InstanceKlass*)k)->java_super();
+
+ // Set the index for the class.
+ cie->set_index(i + 1);
+
+ // Add the class to the subclass array of its superclass.
+ if (super != NULL) {
+ KlassInfoEntry* super_cie = cit.lookup(super);
+ assert(super_cie != NULL, "could not lookup superclass");
+ super_cie->add_subclass(cie);
+ }
+ }
+
+ // Set the do_print flag for each class that should be printed.
+ for(int i = 0; i < elements.length(); i++) {
+ KlassInfoEntry* cie = elements.at(i);
+ if (classname == NULL) {
+ // We are printing all classes.
+ cie->set_do_print(true);
+ } else {
+ // We are only printing the hierarchy of a specific class.
+ if (strcmp(classname, cie->klass()->external_name()) == 0) {
+ KlassHierarchy::set_do_print_for_class_hierarchy(cie, &cit, print_subclasses);
+ }
+ }
+ }
+
+ // Now we do a depth first traversal of the class hierachry. The class_stack will
+ // maintain the list of classes we still need to process. Start things off
+ // by priming it with java.lang.Object.
+ KlassInfoEntry* jlo_cie = cit.lookup(SystemDictionary::Object_klass());
+ assert(jlo_cie != NULL, "could not lookup java.lang.Object");
+ class_stack.push(jlo_cie);
+
+ // Repeatedly pop the top item off the stack, print its class info,
+ // and push all of its subclasses on to the stack. Do this until there
+ // are no classes left on the stack.
+ while (!class_stack.is_empty()) {
+ KlassInfoEntry* curr_cie = class_stack.pop();
+ if (curr_cie->do_print()) {
+ print_class(st, curr_cie, print_interfaces);
+ if (curr_cie->subclasses() != NULL) {
+ // Current class has subclasses, so push all of them onto the stack.
+ for (int i = 0; i < curr_cie->subclasses()->length(); i++) {
+ KlassInfoEntry* cie = curr_cie->subclasses()->at(i);
+ if (cie->do_print()) {
+ class_stack.push(cie);
+ }
+ }
+ }
+ }
+ }
+
+ st->flush();
+}
+
+// Sets the do_print flag for every superclass and subclass of the specified class.
+void KlassHierarchy::set_do_print_for_class_hierarchy(KlassInfoEntry* cie, KlassInfoTable* cit,
+ bool print_subclasses) {
+ // Set do_print for all superclasses of this class.
+ Klass* super = ((InstanceKlass*)cie->klass())->java_super();
+ while (super != NULL) {
+ KlassInfoEntry* super_cie = cit->lookup(super);
+ super_cie->set_do_print(true);
+ super = super->super();
+ }
+
+ // Set do_print for this class and all of its subclasses.
+ Stack <KlassInfoEntry*, mtClass> class_stack;
+ class_stack.push(cie);
+ while (!class_stack.is_empty()) {
+ KlassInfoEntry* curr_cie = class_stack.pop();
+ curr_cie->set_do_print(true);
+ if (print_subclasses && curr_cie->subclasses() != NULL) {
+ // Current class has subclasses, so push all of them onto the stack.
+ for (int i = 0; i < curr_cie->subclasses()->length(); i++) {
+ KlassInfoEntry* cie = curr_cie->subclasses()->at(i);
+ class_stack.push(cie);
+ }
+ }
+ }
+}
+
+static void print_indent(outputStream* st, int indent) {
+ while (indent != 0) {
+ st->print("|");
+ indent--;
+ if (indent != 0) {
+ st->print(" ");
+ }
+ }
+}
+
+// Print the class name and its unique ClassLoader identifer.
+static void print_classname(outputStream* st, Klass* klass) {
+ oop loader_oop = klass->class_loader_data()->class_loader();
+ st->print("%s/", klass->external_name());
+ if (loader_oop == NULL) {
+ st->print("null");
+ } else {
+ st->print(INTPTR_FORMAT, klass->class_loader_data());
+ }
+}
+
+static void print_interface(outputStream* st, Klass* intf_klass, const char* intf_type, int indent) {
+ print_indent(st, indent);
+ st->print(" implements ");
+ print_classname(st, intf_klass);
+ st->print(" (%s intf)\n", intf_type);
+}
+
+void KlassHierarchy::print_class(outputStream* st, KlassInfoEntry* cie, bool print_interfaces) {
+ ResourceMark rm;
+ InstanceKlass* klass = (InstanceKlass*)cie->klass();
+ int indent = 0;
+
+ // Print indentation with proper indicators of superclass.
+ Klass* super = klass->super();
+ while (super != NULL) {
+ super = super->super();
+ indent++;
+ }
+ print_indent(st, indent);
+ if (indent != 0) st->print("--");
+
+ // Print the class name, its unique ClassLoader identifer, and if it is an interface.
+ print_classname(st, klass);
+ if (klass->is_interface()) {
+ st->print(" (intf)");
+ }
+ st->print("\n");
+
+ // Print any interfaces the class has.
+ if (print_interfaces) {
+ Array<Klass*>* local_intfs = klass->local_interfaces();
+ Array<Klass*>* trans_intfs = klass->transitive_interfaces();
+ for (int i = 0; i < local_intfs->length(); i++) {
+ print_interface(st, local_intfs->at(i), "declared", indent);
+ }
+ for (int i = 0; i < trans_intfs->length(); i++) {
+ Klass* trans_interface = trans_intfs->at(i);
+ // Only print transitive interfaces if they are not also declared.
+ if (!local_intfs->contains(trans_interface)) {
+ print_interface(st, trans_interface, "inherited", indent);
+ }
+ }
+ }
+}
+
void KlassInfoHisto::print_class_stats(outputStream* st,
bool csv_format, const char *columns) {
ResourceMark rm;
@@ -320,6 +519,8 @@
elements()->at(i)->set_index(i+1);
}
+ // First iteration is for accumulating stats totals in colsum_table[].
+ // Second iteration is for printing stats for each class.
for (int pass=1; pass<=2; pass++) {
if (pass == 2) {
print_title(st, csv_format, selected, width_table, name_table);
@@ -328,6 +529,7 @@
KlassInfoEntry* e = (KlassInfoEntry*)elements()->at(i);
const Klass* k = e->klass();
+ // Get the stats for this class.
memset(&sz, 0, sizeof(sz));
sz._inst_count = e->count();
sz._inst_bytes = HeapWordSize * e->words();
@@ -335,11 +537,13 @@
sz._total_bytes = sz._ro_bytes + sz._rw_bytes;
if (pass == 1) {
+ // Add the stats for this class to the overall totals.
for (int c=0; c<KlassSizeStats::_num_columns; c++) {
colsum_table[c] += col_table[c];
}
} else {
int super_index = -1;
+ // Print the stats for this class.
if (k->oop_is_instance()) {
Klass* super = ((InstanceKlass*)k)->java_super();
if (super) {
@@ -373,6 +577,8 @@
}
if (pass == 1) {
+ // Calculate the minimum width needed for the column by accounting for the
+ // column header width and the width of the largest value in the column.
for (int c=0; c<KlassSizeStats::_num_columns; c++) {
width_table[c] = col_width(colsum_table[c], name_table[c]);
}
@@ -381,6 +587,7 @@
sz_sum._inst_size = 0;
+ // Print the column totals.
if (csv_format) {
st->print(",");
for (int c=0; c<KlassSizeStats::_num_columns; c++) {
@@ -514,6 +721,7 @@
KlassInfoTable cit(_print_class_stats);
if (!cit.allocation_failed()) {
+ // populate table with object allocation info
size_t missed_count = populate_table(&cit);
if (missed_count != 0) {
st->print_cr("WARNING: Ran out of C-heap; undercounted " SIZE_FORMAT
@@ -533,7 +741,7 @@
histo.sort();
histo.print_histo_on(st, _print_class_stats, _csv_format, _columns);
} else {
- st->print_cr("WARNING: Ran out of C-heap; histogram not generated");
+ st->print_cr("ERROR: Ran out of C-heap; histogram not generated");
}
st->flush();
}
--- a/hotspot/src/share/vm/memory/heapInspection.hpp Fri Feb 13 13:17:13 2015 +0100
+++ b/hotspot/src/share/vm/memory/heapInspection.hpp Wed Feb 11 15:22:43 2015 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2015, 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
@@ -188,11 +188,15 @@
long _instance_count;
size_t _instance_words;
long _index;
+ bool _do_print; // True if we should print this class when printing the class hierarchy.
+ GrowableArray<KlassInfoEntry*>* _subclasses;
public:
KlassInfoEntry(Klass* k, KlassInfoEntry* next) :
- _klass(k), _instance_count(0), _instance_words(0), _next(next), _index(-1)
+ _klass(k), _instance_count(0), _instance_words(0), _next(next), _index(-1),
+ _do_print(false), _subclasses(NULL)
{}
+ ~KlassInfoEntry();
KlassInfoEntry* next() const { return _next; }
bool is_equal(const Klass* k) { return k == _klass; }
Klass* klass() const { return _klass; }
@@ -202,6 +206,10 @@
void set_words(size_t wds) { _instance_words = wds; }
void set_index(long index) { _index = index; }
long index() const { return _index; }
+ GrowableArray<KlassInfoEntry*>* subclasses() const { return _subclasses; }
+ void add_subclass(KlassInfoEntry* cie);
+ void set_do_print(bool do_print) { _do_print = do_print; }
+ bool do_print() const { return _do_print; }
int compare(KlassInfoEntry* e1, KlassInfoEntry* e2);
void print_on(outputStream* st) const;
const char* name() const;
@@ -248,7 +256,7 @@
};
public:
- KlassInfoTable(bool need_class_stats);
+ KlassInfoTable(bool add_all_classes);
~KlassInfoTable();
bool record_instance(const oop obj);
void iterate(KlassInfoClosure* cic);
@@ -256,6 +264,18 @@
size_t size_of_instances_in_words() const;
friend class KlassInfoHisto;
+ friend class KlassHierarchy;
+};
+
+class KlassHierarchy : AllStatic {
+ public:
+ static void print_class_hierarchy(outputStream* st, bool print_interfaces, bool print_subclasses,
+ char* classname);
+
+ private:
+ static void set_do_print_for_class_hierarchy(KlassInfoEntry* cie, KlassInfoTable* cit,
+ bool print_subclasse);
+ static void print_class(outputStream* st, KlassInfoEntry* cie, bool print_subclasses);
};
class KlassInfoHisto : public StackObj {
--- a/hotspot/src/share/vm/runtime/vm_operations.cpp Fri Feb 13 13:17:13 2015 +0100
+++ b/hotspot/src/share/vm/runtime/vm_operations.cpp Wed Feb 11 15:22:43 2015 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,7 @@
#include "compiler/compileBroker.hpp"
#include "compiler/compilerOracle.hpp"
#include "gc_implementation/shared/isGCActiveMark.hpp"
+#include "memory/heapInspection.hpp"
#include "memory/resourceArea.hpp"
#include "oops/symbol.hpp"
#include "runtime/arguments.hpp"
@@ -486,3 +487,9 @@
void VM_PrintCodeCache::doit() {
CodeCache::print_layout(_out);
}
+
+#if INCLUDE_SERVICES
+void VM_PrintClassHierarchy::doit() {
+ KlassHierarchy::print_class_hierarchy(_out, _print_interfaces, _print_subclasses, _classname);
+}
+#endif
--- a/hotspot/src/share/vm/runtime/vm_operations.hpp Fri Feb 13 13:17:13 2015 +0100
+++ b/hotspot/src/share/vm/runtime/vm_operations.hpp Wed Feb 11 15:22:43 2015 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -105,6 +105,7 @@
template(PrintCompileQueue) \
template(PrintCodeList) \
template(PrintCodeCache) \
+ template(PrintClassHierarchy) \
class VM_Operation: public CHeapObj<mtInternal> {
public:
@@ -457,5 +458,21 @@
void doit();
};
+#if INCLUDE_SERVICES
+class VM_PrintClassHierarchy: public VM_Operation {
+ private:
+ outputStream* _out;
+ bool _print_interfaces;
+ bool _print_subclasses;
+ char* _classname;
+
+ public:
+ VM_PrintClassHierarchy(outputStream* st, bool print_interfaces, bool print_subclasses, char* classname) :
+ _out(st), _print_interfaces(print_interfaces), _print_subclasses(print_subclasses),
+ _classname(classname) {}
+ VMOp_Type type() const { return VMOp_PrintClassHierarchy; }
+ void doit();
+};
+#endif // INCLUDE_SERVICES
#endif // SHARE_VM_RUNTIME_VM_OPERATIONS_HPP
--- a/hotspot/src/share/vm/services/diagnosticCommand.cpp Fri Feb 13 13:17:13 2015 +0100
+++ b/hotspot/src/share/vm/services/diagnosticCommand.cpp Wed Feb 11 15:22:43 2015 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2015, 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
@@ -57,6 +57,7 @@
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<HeapDumpDCmd>(DCmd_Source_Internal | DCmd_Source_AttachAPI, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassHistogramDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassStatsDCmd>(full_export, true, false));
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassHierarchyDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<SymboltableDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<StringtableDCmd>(full_export, true, false));
#endif // INCLUDE_SERVICES
@@ -695,3 +696,35 @@
VMThread::execute(&printCodeCacheOp);
}
+#if INCLUDE_SERVICES
+ClassHierarchyDCmd::ClassHierarchyDCmd(outputStream* output, bool heap) :
+ DCmdWithParser(output, heap),
+ _print_interfaces("-i", "Inherited interfaces should be printed.", "BOOLEAN", false, "false"),
+ _print_subclasses("-s", "If a classname is specified, print its subclasses. "
+ "Otherwise only its superclasses are printed.", "BOOLEAN", false, "false"),
+ _classname("classname", "Name of class whose hierarchy should be printed. "
+ "If not specified, all class hierarchies are printed.",
+ "STRING", false) {
+ _dcmdparser.add_dcmd_option(&_print_interfaces);
+ _dcmdparser.add_dcmd_option(&_print_subclasses);
+ _dcmdparser.add_dcmd_argument(&_classname);
+}
+
+void ClassHierarchyDCmd::execute(DCmdSource source, TRAPS) {
+ VM_PrintClassHierarchy printClassHierarchyOp(output(), _print_interfaces.value(),
+ _print_subclasses.value(), _classname.value());
+ VMThread::execute(&printClassHierarchyOp);
+}
+
+int ClassHierarchyDCmd::num_arguments() {
+ ResourceMark rm;
+ ClassHierarchyDCmd* dcmd = new ClassHierarchyDCmd(NULL, false);
+ if (dcmd != NULL) {
+ DCmdMark mark(dcmd);
+ return dcmd->_dcmdparser.num_arguments();
+ } else {
+ return 0;
+ }
+}
+
+#endif
--- a/hotspot/src/share/vm/services/diagnosticCommand.hpp Fri Feb 13 13:17:13 2015 +0100
+++ b/hotspot/src/share/vm/services/diagnosticCommand.hpp Wed Feb 11 15:22:43 2015 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2015, 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
@@ -271,6 +271,34 @@
virtual void execute(DCmdSource source, TRAPS);
};
+
+class ClassHierarchyDCmd : public DCmdWithParser {
+protected:
+ DCmdArgument<bool> _print_interfaces; // true if inherited interfaces should be printed.
+ DCmdArgument<bool> _print_subclasses; // true if subclasses of the specified classname should be printed.
+ DCmdArgument<char*> _classname; // Optional single class name whose hierarchy should be printed.
+public:
+ ClassHierarchyDCmd(outputStream* output, bool heap);
+ static const char* name() {
+ return "VM.class_hierarchy";
+ }
+ static const char* description() {
+ return "Print a list of all loaded classes, indented to show the class hiearchy. "
+ "The name of each class is followed by the ClassLoaderData* of its ClassLoader, "
+ "or \"null\" if loaded by the bootstrap class loader.";
+ }
+ static const char* impact() {
+ return "Medium: Depends on number of loaded classes.";
+ }
+ static const JavaPermission permission() {
+ JavaPermission p = {"java.lang.management.ManagementPermission",
+ "monitor", NULL};
+ return p;
+ }
+ static int num_arguments();
+ virtual void execute(DCmdSource source, TRAPS);
+};
+
// See also: thread_dump in attachListener.cpp
class ThreadDumpDCmd : public DCmdWithParser {
protected:
--- a/hotspot/test/Makefile Fri Feb 13 13:17:13 2015 +0100
+++ b/hotspot/test/Makefile Wed Feb 11 15:22:43 2015 -0800
@@ -271,7 +271,7 @@
# Report details on all failed or error tests, times too
JTREG_BASIC_OPTIONS += -v:fail,error,time
# Retain all files for failing tests
-JTREG_BASIC_OPTIONS += -retain:fail,error
+JTREG_BASIC_OPTIONS += -retain:all
# Ignore tests are not run and completely silent about it
JTREG_IGNORE_OPTION = -ignore:quiet
JTREG_BASIC_OPTIONS += $(JTREG_IGNORE_OPTION)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/serviceability/dcmd/vm/ClassHierarchyTest.java Wed Feb 11 15:22:43 2015 -0800
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Test of diagnostic command VM.class_hierarchy
+ * @library /testlibrary
+ * @build com.oracle.java.testlibrary.*
+ * @build com.oracle.java.testlibrary.dcmd.*
+ * @run testng ClassHierarchyTest
+ */
+
+import org.testng.annotations.Test;
+import org.testng.Assert;
+
+import com.oracle.java.testlibrary.OutputAnalyzer;
+import com.oracle.java.testlibrary.dcmd.CommandExecutor;
+import com.oracle.java.testlibrary.dcmd.JMXExecutor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ClassHierarchyTest {
+
+ // $> jcmd DcmdTestClass VM.class_hierarchy DcmdTestClass | grep DcmdTestClass\$\$Lambda
+ // |--DcmdTestClass$$Lambda$1/4081552/0xa529fbb0
+
+ // > VM.class_hierarchy DcmdBaseClass
+ // java.lang.Object/null
+ // |--DcmdBaseClass/0xa4abcd48
+
+ // > VM.class_hierarchy DcmdBaseClass -s
+ // java.lang.Object/null
+ // |--DcmdBaseClass/0xa4abcd48
+ // | |--DcmdTestClass/0xa4abcd48
+
+ // > VM.class_hierarchy DcmdBaseClass -i -s
+ // java.lang.Object/null
+ // |--DcmdBaseClass/0xa4abcd48
+ // | implements Intf2/0xa4abcd48 (declared intf)
+ // | implements Intf1/0xa4abcd48 (inherited intf)
+ // | |--DcmdTestClass/0xa4abcd48
+ // | | implements Intf1/0xa4abcd48 (inherited intf)
+ // | | implements Intf2/0xa4abcd48 (inherited intf)
+
+ static Pattern expected_lambda_line =
+ Pattern.compile("\\|--DcmdTestClass\\$\\$Lambda.*");
+
+ static Pattern expected_lines[] = {
+ Pattern.compile("java.lang.Object/null"),
+ Pattern.compile("\\|--DcmdBaseClass/0x(\\p{XDigit}*)"),
+ Pattern.compile("\\| implements Intf2/0x(\\p{XDigit}*) \\(declared intf\\)"),
+ Pattern.compile("\\| implements Intf1/0x(\\p{XDigit}*) \\(inherited intf\\)"),
+ Pattern.compile("\\| \\|--DcmdTestClass/0x(\\p{XDigit}*)"),
+ Pattern.compile("\\| \\| implements Intf1/0x(\\p{XDigit}*) \\(inherited intf\\)"),
+ Pattern.compile("\\| \\| implements Intf2/0x(\\p{XDigit}*) \\(inherited intf\\)")
+ };
+
+ public void run(CommandExecutor executor) throws ClassNotFoundException {
+ OutputAnalyzer output;
+ Iterator<String> lines;
+ int i;
+
+ // Load our test class whose hierarchy we will print.
+ Class<?> c = Class.forName("DcmdTestClass");
+
+ // Verify the presence of the lamba anonymous class
+ output = executor.execute("VM.class_hierarchy");
+ lines = output.asLines().iterator();
+ Boolean foundMatch = false;
+ while (lines.hasNext()) {
+ String line = lines.next();
+ Matcher m = expected_lambda_line.matcher(line);
+ if (m.matches()) {
+ foundMatch = true;
+ break;
+ }
+ }
+ if (!foundMatch) {
+ Assert.fail("Failed to find lamda class");
+ }
+
+ // Verify the output for the simple hierachry of just DcmdBaseClass.
+ output = executor.execute("VM.class_hierarchy DcmdBaseClass");
+ lines = output.asLines().iterator();
+ i = 0;
+ while (lines.hasNext()) {
+ String line = lines.next();
+ Matcher m = expected_lines[i].matcher(line);
+ i++;
+ if (!m.matches()) {
+ Assert.fail("Failed to match line #" + i + ": " + line);
+ }
+ // Should only be two lines of output in this form.
+ if (i == 2) break;
+ }
+ if (lines.hasNext()) {
+ String line = lines.next();
+ Assert.fail("Unexpected dcmd output: " + line);
+ }
+
+ // Verify the output for the full hierarchy of DcmdBaseClass, but without interfaces.
+ output = executor.execute("VM.class_hierarchy DcmdBaseClass -s");
+ lines = output.asLines().iterator();
+ i = 0;
+ while (lines.hasNext()) {
+ String line = lines.next();
+ Matcher m = expected_lines[i].matcher(line);
+ i++;
+ if (!m.matches()) {
+ Assert.fail("Failed to match line #" + i + ": " + line);
+ }
+ // "implements" lines should not be in this output.
+ if (i == 2 || i == 4) i += 2;
+ }
+ if (lines.hasNext()) {
+ String line = lines.next();
+ Assert.fail("Unexpected dcmd output: " + line);
+ }
+
+ // Verify the output for the full hierarchy of DcmdBaseClass, including interfaces.
+ output = executor.execute("VM.class_hierarchy DcmdBaseClass -i -s");
+ lines = output.asLines().iterator();
+ i = 0;
+ String classLoaderAddr = null;
+ while (lines.hasNext()) {
+ String line = lines.next();
+ Matcher m = expected_lines[i].matcher(line);
+ i++;
+ if (!m.matches()) {
+ Assert.fail("Failed to match line #" + i + ": " + line);
+ }
+ if (i == 2) {
+ // Fetch the ClassLoader address, which should be the same in
+ // subsequent lines.
+ classLoaderAddr = m.group(1);
+ System.out.println(classLoaderAddr);
+ } else if (i > 2) {
+ if (!classLoaderAddr.equals(m.group(1))) {
+ Assert.fail("Classloader address didn't match on line #"
+ + i + ": " + line);
+ }
+ }
+ if (i == expected_lines.length) break;
+ }
+ if (lines.hasNext()) {
+ String line = lines.next();
+ Assert.fail("Unexpected dcmd output: " + line);
+ }
+ }
+
+ @Test
+ public void jmx() throws ClassNotFoundException {
+ run(new JMXExecutor());
+ }
+}
+
+interface Intf1 {
+}
+
+interface Intf2 extends Intf1 {
+}
+
+class DcmdBaseClass implements Intf2 {
+}
+
+class DcmdTestClass extends DcmdBaseClass {
+ static {
+ // Force creation of anonymous class (for the lambdaform).
+ Runnable r = () -> System.out.println("Hello");
+ r.run();
+ }
+}