8054888: Runtime: Add Diagnostic Command that prints the class hierarchy
authorcjplummer
Wed, 11 Feb 2015 15:22:43 -0800
changeset 29071 73f45d04ad7a
parent 29069 938c1c97ad3b
child 29072 4d325459c405
8054888: Runtime: Add Diagnostic Command that prints the class hierarchy Summary: Added the VM.class_hierarhcy DCMD Reviewed-by: stefank, miauno
hotspot/src/share/vm/memory/heapInspection.cpp
hotspot/src/share/vm/memory/heapInspection.hpp
hotspot/src/share/vm/runtime/vm_operations.cpp
hotspot/src/share/vm/runtime/vm_operations.hpp
hotspot/src/share/vm/services/diagnosticCommand.cpp
hotspot/src/share/vm/services/diagnosticCommand.hpp
hotspot/test/Makefile
hotspot/test/serviceability/dcmd/vm/ClassHierarchyTest.java
--- 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();
+    }
+}