|
1 /* |
|
2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. |
|
3 * Copyright (c) 2018 SAP SE. All rights reserved. |
|
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
5 * |
|
6 * This code is free software; you can redistribute it and/or modify it |
|
7 * under the terms of the GNU General Public License version 2 only, as |
|
8 * published by the Free Software Foundation. |
|
9 * |
|
10 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
13 * version 2 for more details (a copy is included in the LICENSE file that |
|
14 * accompanied this code). |
|
15 * |
|
16 * You should have received a copy of the GNU General Public License version |
|
17 * 2 along with this work; if not, write to the Free Software Foundation, |
|
18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
19 * |
|
20 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
21 * or visit www.oracle.com if you need additional information or have any |
|
22 * questions. |
|
23 * |
|
24 */ |
|
25 |
|
26 #include "precompiled.hpp" |
|
27 |
|
28 #include "classfile/classLoaderData.inline.hpp" |
|
29 #include "classfile/classLoaderHierarchyDCmd.hpp" |
|
30 #include "memory/allocation.hpp" |
|
31 #include "memory/resourceArea.hpp" |
|
32 #include "runtime/safepoint.hpp" |
|
33 #include "utilities/globalDefinitions.hpp" |
|
34 #include "utilities/ostream.hpp" |
|
35 |
|
36 |
|
37 ClassLoaderHierarchyDCmd::ClassLoaderHierarchyDCmd(outputStream* output, bool heap) |
|
38 : DCmdWithParser(output, heap) |
|
39 , _show_classes("show-classes", "Print loaded classes.", "BOOLEAN", false, "false") |
|
40 , _verbose("verbose", "Print detailed information.", "BOOLEAN", false, "false") { |
|
41 _dcmdparser.add_dcmd_option(&_show_classes); |
|
42 _dcmdparser.add_dcmd_option(&_verbose); |
|
43 } |
|
44 |
|
45 |
|
46 int ClassLoaderHierarchyDCmd::num_arguments() { |
|
47 ResourceMark rm; |
|
48 ClassLoaderHierarchyDCmd* dcmd = new ClassLoaderHierarchyDCmd(NULL, false); |
|
49 if (dcmd != NULL) { |
|
50 DCmdMark mark(dcmd); |
|
51 return dcmd->_dcmdparser.num_arguments(); |
|
52 } else { |
|
53 return 0; |
|
54 } |
|
55 } |
|
56 |
|
57 // Helper class for drawing the branches to the left of a node. |
|
58 class BranchTracker : public StackObj { |
|
59 // "<x>" |
|
60 // " |---<y>" |
|
61 // " | | |
|
62 // " | <z>" |
|
63 // " | |---<z1> |
|
64 // " | |---<z2> |
|
65 // ^^^^^^^ ^^^ |
|
66 // A B |
|
67 |
|
68 // Some terms for the graphics: |
|
69 // - branch: vertical connection between a node's ancestor to a later sibling. |
|
70 // - branchwork: (A) the string to print as a prefix at the start of each line, contains all branches. |
|
71 // - twig (B): Length of the dashed line connecting a node to its branch. |
|
72 // - branch spacing: how many spaces between branches are printed. |
|
73 |
|
74 public: |
|
75 |
|
76 enum { max_depth = 64, twig_len = 2, branch_spacing = 5 }; |
|
77 |
|
78 private: |
|
79 |
|
80 char _branches[max_depth]; |
|
81 int _pos; |
|
82 |
|
83 public: |
|
84 BranchTracker() |
|
85 : _pos(0) {} |
|
86 |
|
87 void push(bool has_branch) { |
|
88 if (_pos < max_depth) { |
|
89 _branches[_pos] = has_branch ? '|' : ' '; |
|
90 } |
|
91 _pos ++; // beyond max depth, omit branch drawing but do count on. |
|
92 } |
|
93 |
|
94 void pop() { |
|
95 assert(_pos > 0, "must be"); |
|
96 _pos --; |
|
97 } |
|
98 |
|
99 void print(outputStream* st) { |
|
100 for (int i = 0; i < _pos; i ++) { |
|
101 st->print("%c%.*s", _branches[i], branch_spacing, " "); |
|
102 } |
|
103 } |
|
104 |
|
105 class Mark { |
|
106 BranchTracker& _tr; |
|
107 public: |
|
108 Mark(BranchTracker& tr, bool has_branch_here) |
|
109 : _tr(tr) { _tr.push(has_branch_here); } |
|
110 ~Mark() { _tr.pop(); } |
|
111 }; |
|
112 |
|
113 }; // end: BranchTracker |
|
114 |
|
115 struct LoadedClassInfo : public ResourceObj { |
|
116 public: |
|
117 LoadedClassInfo* _next; |
|
118 Klass* const _klass; |
|
119 const ClassLoaderData* const _cld; |
|
120 |
|
121 LoadedClassInfo(Klass* klass, const ClassLoaderData* cld) |
|
122 : _klass(klass), _cld(cld) {} |
|
123 |
|
124 }; |
|
125 |
|
126 class LoaderTreeNode : public ResourceObj { |
|
127 |
|
128 // We walk the CLDG and, for each CLD which is non-anonymous, add |
|
129 // a tree node. To add a node we need its parent node; if it itself |
|
130 // does not exist yet, we add a preliminary node for it. This preliminary |
|
131 // node just contains its loader oop; later, when encountering its CLD in |
|
132 // our CLDG walk, we complete the missing information in this node. |
|
133 |
|
134 const oop _loader_oop; |
|
135 const ClassLoaderData* _cld; |
|
136 |
|
137 LoaderTreeNode* _child; |
|
138 LoaderTreeNode* _next; |
|
139 |
|
140 LoadedClassInfo* _classes; |
|
141 int _num_classes; |
|
142 |
|
143 LoadedClassInfo* _anon_classes; |
|
144 int _num_anon_classes; |
|
145 |
|
146 void print_with_childs(outputStream* st, BranchTracker& branchtracker, |
|
147 bool print_classes, bool verbose) const { |
|
148 |
|
149 ResourceMark rm; |
|
150 |
|
151 if (_cld == NULL) { |
|
152 // Not sure how this could happen: we added a preliminary node for a parent but then never encountered |
|
153 // its CLD? |
|
154 return; |
|
155 } |
|
156 |
|
157 // Retrieve information. |
|
158 const Klass* const loader_klass = _cld->class_loader_klass(); |
|
159 const Symbol* const loader_name = _cld->class_loader_name(); |
|
160 |
|
161 branchtracker.print(st); |
|
162 |
|
163 // e.g. "+--- jdk.internal.reflect.DelegatingClassLoader" |
|
164 st->print("+%.*s", BranchTracker::twig_len, "----------"); |
|
165 if (_cld->is_the_null_class_loader_data()) { |
|
166 st->print(" <bootstrap>"); |
|
167 } else { |
|
168 if (loader_name != NULL) { |
|
169 st->print(" \"%s\",", loader_name->as_C_string()); |
|
170 } |
|
171 st->print(" %s", loader_klass != NULL ? loader_klass->external_name() : "??"); |
|
172 st->print(" {" PTR_FORMAT "}", p2i(_loader_oop)); |
|
173 } |
|
174 st->cr(); |
|
175 |
|
176 // Output following this node (node details and child nodes) - up to the next sibling node |
|
177 // needs to be prefixed with "|" if there is a follow up sibling. |
|
178 const bool have_sibling = _next != NULL; |
|
179 BranchTracker::Mark trm(branchtracker, have_sibling); |
|
180 |
|
181 { |
|
182 // optional node details following this node needs to be prefixed with "|" |
|
183 // if there are follow up child nodes. |
|
184 const bool have_child = _child != NULL; |
|
185 BranchTracker::Mark trm(branchtracker, have_child); |
|
186 |
|
187 // Empty line |
|
188 branchtracker.print(st); |
|
189 st->cr(); |
|
190 |
|
191 const int indentation = 18; |
|
192 |
|
193 if (verbose) { |
|
194 branchtracker.print(st); |
|
195 st->print_cr("%*s " PTR_FORMAT, indentation, "Loader Data:", p2i(_cld)); |
|
196 branchtracker.print(st); |
|
197 st->print_cr("%*s " PTR_FORMAT, indentation, "Loader Klass:", p2i(loader_klass)); |
|
198 |
|
199 // Empty line |
|
200 branchtracker.print(st); |
|
201 st->cr(); |
|
202 } |
|
203 |
|
204 if (print_classes) { |
|
205 |
|
206 if (_classes != NULL) { |
|
207 for (LoadedClassInfo* lci = _classes; lci; lci = lci->_next) { |
|
208 branchtracker.print(st); |
|
209 if (lci == _classes) { // first iteration |
|
210 st->print("%*s ", indentation, "Classes:"); |
|
211 } else { |
|
212 st->print("%*s ", indentation, ""); |
|
213 } |
|
214 st->print("%s", lci->_klass->external_name()); |
|
215 st->cr(); |
|
216 // Non-anonymous classes should live in the primary CLD of its loader |
|
217 assert(lci->_cld == _cld, "must be"); |
|
218 } |
|
219 branchtracker.print(st); |
|
220 st->print("%*s ", indentation, ""); |
|
221 st->print_cr("(%u class%s)", _num_classes, (_num_classes == 1) ? "" : "es"); |
|
222 |
|
223 // Empty line |
|
224 branchtracker.print(st); |
|
225 st->cr(); |
|
226 } |
|
227 |
|
228 if (_anon_classes != NULL) { |
|
229 for (LoadedClassInfo* lci = _anon_classes; lci; lci = lci->_next) { |
|
230 branchtracker.print(st); |
|
231 if (lci == _anon_classes) { // first iteration |
|
232 st->print("%*s ", indentation, "Anonymous Classes:"); |
|
233 } else { |
|
234 st->print("%*s ", indentation, ""); |
|
235 } |
|
236 st->print("%s", lci->_klass->external_name()); |
|
237 // For anonymous classes, also print CLD if verbose. Should be a different one than the primary CLD. |
|
238 assert(lci->_cld != _cld, "must be"); |
|
239 if (verbose) { |
|
240 st->print(" (CLD: " PTR_FORMAT ")", p2i(lci->_cld)); |
|
241 } |
|
242 st->cr(); |
|
243 } |
|
244 branchtracker.print(st); |
|
245 st->print("%*s ", indentation, ""); |
|
246 st->print_cr("(%u anonymous class%s)", _num_anon_classes, (_num_anon_classes == 1) ? "" : "es"); |
|
247 |
|
248 // Empty line |
|
249 branchtracker.print(st); |
|
250 st->cr(); |
|
251 } |
|
252 |
|
253 } // end: print_classes |
|
254 |
|
255 } // Pop branchtracker mark |
|
256 |
|
257 // Print children, recursively |
|
258 LoaderTreeNode* c = _child; |
|
259 while (c != NULL) { |
|
260 c->print_with_childs(st, branchtracker, print_classes, verbose); |
|
261 c = c->_next; |
|
262 } |
|
263 |
|
264 } |
|
265 |
|
266 public: |
|
267 |
|
268 LoaderTreeNode(const oop loader_oop) |
|
269 : _loader_oop(loader_oop), _cld(NULL) |
|
270 , _child(NULL), _next(NULL) |
|
271 , _classes(NULL), _anon_classes(NULL) |
|
272 , _num_classes(0), _num_anon_classes(0) {} |
|
273 |
|
274 void set_cld(const ClassLoaderData* cld) { |
|
275 _cld = cld; |
|
276 } |
|
277 |
|
278 void add_child(LoaderTreeNode* info) { |
|
279 info->_next = _child; |
|
280 _child = info; |
|
281 } |
|
282 |
|
283 void add_sibling(LoaderTreeNode* info) { |
|
284 assert(info->_next == NULL, "must be"); |
|
285 info->_next = _next; |
|
286 _next = info; |
|
287 } |
|
288 |
|
289 void add_classes(LoadedClassInfo* first_class, int num_classes, bool anonymous) { |
|
290 LoadedClassInfo** p_list_to_add_to = anonymous ? &_anon_classes : &_classes; |
|
291 // Search tail. |
|
292 while ((*p_list_to_add_to) != NULL) { |
|
293 p_list_to_add_to = &(*p_list_to_add_to)->_next; |
|
294 } |
|
295 *p_list_to_add_to = first_class; |
|
296 if (anonymous) { |
|
297 _num_anon_classes += num_classes; |
|
298 } else { |
|
299 _num_classes += num_classes; |
|
300 } |
|
301 } |
|
302 |
|
303 const ClassLoaderData* cld() const { |
|
304 return _cld; |
|
305 } |
|
306 |
|
307 const oop loader_oop() const { |
|
308 return _loader_oop; |
|
309 } |
|
310 |
|
311 LoaderTreeNode* find(const oop loader_oop) { |
|
312 LoaderTreeNode* result = NULL; |
|
313 if (_loader_oop == loader_oop) { |
|
314 result = this; |
|
315 } else { |
|
316 LoaderTreeNode* c = _child; |
|
317 while (c != NULL && result == NULL) { |
|
318 result = c->find(loader_oop); |
|
319 c = c->_next; |
|
320 } |
|
321 } |
|
322 return result; |
|
323 } |
|
324 |
|
325 void print_with_childs(outputStream* st, bool print_classes, bool print_add_info) const { |
|
326 BranchTracker bwt; |
|
327 print_with_childs(st, bwt, print_classes, print_add_info); |
|
328 } |
|
329 |
|
330 }; |
|
331 |
|
332 class LoadedClassCollectClosure : public KlassClosure { |
|
333 public: |
|
334 LoadedClassInfo* _list; |
|
335 const ClassLoaderData* _cld; |
|
336 int _num_classes; |
|
337 LoadedClassCollectClosure(const ClassLoaderData* cld) |
|
338 : _list(NULL), _cld(cld), _num_classes(0) {} |
|
339 void do_klass(Klass* k) { |
|
340 LoadedClassInfo* lki = new LoadedClassInfo(k, _cld); |
|
341 lki->_next = _list; |
|
342 _list = lki; |
|
343 _num_classes ++; |
|
344 } |
|
345 }; |
|
346 |
|
347 class LoaderInfoScanClosure : public CLDClosure { |
|
348 |
|
349 const bool _print_classes; |
|
350 const bool _verbose; |
|
351 LoaderTreeNode* _root; |
|
352 |
|
353 static void fill_in_classes(LoaderTreeNode* info, const ClassLoaderData* cld) { |
|
354 assert(info != NULL && cld != NULL, "must be"); |
|
355 LoadedClassCollectClosure lccc(cld); |
|
356 const_cast<ClassLoaderData*>(cld)->classes_do(&lccc); |
|
357 if (lccc._num_classes > 0) { |
|
358 info->add_classes(lccc._list, lccc._num_classes, cld->is_anonymous()); |
|
359 } |
|
360 } |
|
361 |
|
362 LoaderTreeNode* find_node_or_add_empty_node(oop loader_oop) { |
|
363 |
|
364 assert(_root != NULL, "root node must exist"); |
|
365 |
|
366 if (loader_oop == NULL) { |
|
367 return _root; |
|
368 } |
|
369 |
|
370 // Check if a node for this oop already exists. |
|
371 LoaderTreeNode* info = _root->find(loader_oop); |
|
372 |
|
373 if (info == NULL) { |
|
374 // It does not. Create a node. |
|
375 info = new LoaderTreeNode(loader_oop); |
|
376 |
|
377 // Add it to tree. |
|
378 LoaderTreeNode* parent_info = NULL; |
|
379 |
|
380 // Recursively add parent nodes if needed. |
|
381 const oop parent_oop = java_lang_ClassLoader::parent(loader_oop); |
|
382 if (parent_oop == NULL) { |
|
383 parent_info = _root; |
|
384 } else { |
|
385 parent_info = find_node_or_add_empty_node(parent_oop); |
|
386 } |
|
387 assert(parent_info != NULL, "must be"); |
|
388 |
|
389 parent_info->add_child(info); |
|
390 } |
|
391 return info; |
|
392 } |
|
393 |
|
394 |
|
395 public: |
|
396 LoaderInfoScanClosure(bool print_classes, bool verbose) |
|
397 : _print_classes(print_classes), _verbose(verbose), _root(NULL) { |
|
398 _root = new LoaderTreeNode(NULL); |
|
399 } |
|
400 |
|
401 void print_results(outputStream* st) const { |
|
402 _root->print_with_childs(st, _print_classes, _verbose); |
|
403 } |
|
404 |
|
405 void do_cld (ClassLoaderData* cld) { |
|
406 |
|
407 // We do not display unloading loaders, for now. |
|
408 if (cld->is_unloading()) { |
|
409 return; |
|
410 } |
|
411 |
|
412 const oop loader_oop = cld->class_loader(); |
|
413 |
|
414 LoaderTreeNode* info = find_node_or_add_empty_node(loader_oop); |
|
415 assert(info != NULL, "must be"); |
|
416 |
|
417 // Update CLD in node, but only if this is the primary CLD for this loader. |
|
418 if (cld->is_anonymous() == false) { |
|
419 assert(info->cld() == NULL, "there should be only one primary CLD per loader"); |
|
420 info->set_cld(cld); |
|
421 } |
|
422 |
|
423 // Add classes. |
|
424 fill_in_classes(info, cld); |
|
425 } |
|
426 |
|
427 }; |
|
428 |
|
429 |
|
430 class ClassLoaderHierarchyVMOperation : public VM_Operation { |
|
431 outputStream* const _out; |
|
432 const bool _show_classes; |
|
433 const bool _verbose; |
|
434 public: |
|
435 ClassLoaderHierarchyVMOperation(outputStream* out, bool show_classes, bool verbose) : |
|
436 _out(out), _show_classes(show_classes), _verbose(verbose) |
|
437 {} |
|
438 |
|
439 VMOp_Type type() const { |
|
440 return VMOp_ClassLoaderHierarchyOperation; |
|
441 } |
|
442 |
|
443 void doit() { |
|
444 assert(SafepointSynchronize::is_at_safepoint(), "must be a safepoint"); |
|
445 ResourceMark rm; |
|
446 LoaderInfoScanClosure cl (_show_classes, _verbose); |
|
447 ClassLoaderDataGraph::cld_do(&cl); |
|
448 cl.print_results(_out); |
|
449 } |
|
450 }; |
|
451 |
|
452 // This command needs to be executed at a safepoint. |
|
453 void ClassLoaderHierarchyDCmd::execute(DCmdSource source, TRAPS) { |
|
454 ClassLoaderHierarchyVMOperation op(output(), _show_classes.value(), _verbose.value()); |
|
455 VMThread::execute(&op); |
|
456 } |