From a2bbf228f5c7d007e1165af8e63dda9786b5e876 Mon Sep 17 00:00:00 2001 From: Kevin Walls Date: Mon, 8 Jun 2026 15:39:57 +0100 Subject: [PATCH] 8327246: Add a jcmd diagnostic command to list Class origin URLs where possible --- src/hotspot/share/oops/instanceKlass.cpp | 62 ++++++++++++++++--- src/hotspot/share/oops/instanceKlass.hpp | 6 +- .../share/services/diagnosticCommand.cpp | 30 ++++----- .../share/services/diagnosticCommand.hpp | 10 ++- src/jdk.jcmd/share/man/jcmd.md | 32 +++++++--- .../runtime/CommandLine/PrintClasses.java | 6 +- 6 files changed, 109 insertions(+), 37 deletions(-) diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index fd1cf1b1457..e07517d63d4 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -36,6 +36,7 @@ #include "classfile/classLoaderData.inline.hpp" #include "classfile/javaClasses.hpp" #include "classfile/moduleEntry.hpp" +#include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/verifier.hpp" @@ -2364,8 +2365,8 @@ Method* InstanceKlass::lookup_method_in_all_interfaces(Symbol* name, return nullptr; } -PrintClassClosure::PrintClassClosure(outputStream* st, bool verbose) - :_st(st), _verbose(verbose) { +PrintClassClosure::PrintClassClosure(outputStream* st, bool verbose, bool location) + :_st(st), _verbose(verbose), _location(location), _aot_statics(0), _aot_dynamics(0) { ResourceMark rm; _st->print("%-18s ", "KlassAddr"); _st->print("%-4s ", "Size"); @@ -2382,27 +2383,68 @@ void PrintClassClosure::do_klass(Klass* k) { // klass size _st->print("%4d ", k->size()); // initialization state - if (k->is_instance_klass()) { - _st->print("%-20s ",InstanceKlass::cast(k)->init_state_name()); - } else { - _st->print("%-20s ",""); - } + + InstanceKlass* ik = k->is_instance_klass() ? InstanceKlass::cast(k) : nullptr; + _st->print("%-20s ", ik != nullptr ? InstanceKlass::cast(k)->init_state_name() : ""); + // misc flags(Changes should synced with ClassesDCmd::ClassesDCmd help doc) char buf[10]; int i = 0; if (k->has_finalizer()) buf[i++] = 'F'; - if (k->is_instance_klass()) { - InstanceKlass* ik = InstanceKlass::cast(k); + if (ik != nullptr) { if (ik->has_final_method()) buf[i++] = 'f'; if (ik->is_rewritten()) buf[i++] = 'W'; if (ik->is_contended()) buf[i++] = 'C'; if (ik->has_been_redefined()) buf[i++] = 'R'; - if (ik->in_aot_cache()) buf[i++] = 'S'; + if (ik->in_aot_cache()) { + buf[i++] = 'S'; + + if (_location) { + if (AOTMetaspace::in_aot_cache_static_region((void*) k)) { + _aot_statics++; + buf[i++] = 's'; + } else if (AOTMetaspace::in_aot_cache_dynamic_region((void*) k)) { + _aot_dynamics++; + buf[i++] = 'd'; + } + } + } } buf[i++] = '\0'; _st->print("%-7s ", buf); // klass name _st->print("%-5s ", k->external_name()); + + if (ik != nullptr && _location) { + oop pd = java_lang_Class::protection_domain(k->java_mirror()); + + if (pd != nullptr) { + assert(pd->klass()->is_instance_klass(), "pd klass is not InstanceKlass"); + TempNewSymbol css = SymbolTable::new_symbol("codesource"); + TempNewSymbol csss = SymbolTable::new_symbol("Ljava/security/CodeSource;"); + fieldDescriptor csfd; + + if (InstanceKlass::cast(pd->klass())->find_field(css, csss, &csfd)) { + oop cs = pd->obj_field(csfd.offset()); + + if (cs != nullptr) { + assert(cs->klass()->is_instance_klass(), "cs klass is not InstanceKlass"); + fieldDescriptor locfd; + TempNewSymbol csls = SymbolTable::new_symbol("locationNoFragString"); + TempNewSymbol cslss = SymbolTable::new_symbol("Ljava/lang/String;"); + + if (InstanceKlass::cast(cs->klass())->find_field(csls, cslss, &locfd)) { + oop loc = cs->obj_field(locfd.offset()); + assert(loc == nullptr || loc->klass() == vmClasses::String_klass(), "locationNoFragString field is not a String"); + + if (loc != nullptr) { + java_lang_String::print(loc, _st, JVM_MAXPATHLEN); + } + } + } + } + } + } // end _st->cr(); if (_verbose) { diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 41f176330fa..968f8cbaaa3 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -1207,8 +1207,12 @@ class PrintClassClosure : public KlassClosure { private: outputStream* _st; bool _verbose; + bool _location; public: - PrintClassClosure(outputStream* st, bool verbose); + unsigned int _aot_statics; + unsigned int _aot_dynamics; + + PrintClassClosure(outputStream* st, bool verbose, bool location); void do_klass(Klass* k); }; diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index f2fa114133e..01da2d25679 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, 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 @@ -25,6 +25,7 @@ #include "cds/aotMetaspace.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" +#include "cds/filemap.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/classLoaderHierarchyDCmd.hpp" #include "classfile/classLoaderStats.hpp" @@ -947,36 +948,37 @@ void ClassHierarchyDCmd::execute(DCmdSource source, TRAPS) { ClassesDCmd::ClassesDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), - _verbose("-verbose", - "Dump the detailed content of a Java class. " - "Some classes are annotated with flags: " - "F = has, or inherits, a non-empty finalize method, " - "f = has final method, " - "W = methods rewritten, " - "C = marked with @Contended annotation, " - "R = has been redefined, " - "S = is shared class", - "BOOLEAN", false, "false") { + _verbose("-verbose", "Dump the detailed content of a Java class", "BOOLEAN", false, "false"), + _location("-location", "Print class file location URL (if available)", "BOOLEAN", false, "false") { _dcmdparser.add_dcmd_option(&_verbose); + _dcmdparser.add_dcmd_option(&_location); } class VM_PrintClasses : public VM_Operation { private: outputStream* _out; bool _verbose; + bool _location; public: - VM_PrintClasses(outputStream* out, bool verbose) : _out(out), _verbose(verbose) {} + VM_PrintClasses(outputStream* out, bool verbose, bool location) : _out(out), _verbose(verbose), _location(location) {} virtual VMOp_Type type() const { return VMOp_PrintClasses; } virtual void doit() { - PrintClassClosure closure(_out, _verbose); + PrintClassClosure closure(_out, _verbose, _location); ClassLoaderDataGraph::classes_do(&closure); + + if (_location) { + if (closure._aot_statics > 0) + _out->print_cr("\n%d classes shared from static cache: %s", closure._aot_statics, FileMapInfo::current_info()->full_path()); + if (closure._aot_dynamics > 0) + _out->print_cr("\n%d classes shared from dynamic cache: %s", closure._aot_dynamics, FileMapInfo::dynamic_info()->full_path()); + } } }; void ClassesDCmd::execute(DCmdSource source, TRAPS) { - VM_PrintClasses vmop(output(), _verbose.value()); + VM_PrintClasses vmop(output(), _verbose.value(), _location.value()); VMThread::execute(&vmop); } diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp index 97ceb19d0ad..a6320bad627 100644 --- a/src/hotspot/share/services/diagnosticCommand.hpp +++ b/src/hotspot/share/services/diagnosticCommand.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, 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 @@ -744,16 +744,20 @@ public: }; class ClassesDCmd : public DCmdWithParser { +private: + static constexpr const char *desc = "Print all loaded classes,\nclasses are annotated with flags:\n F = has, or inherits, a non-empty finalize method,\n f = has final method,\n W = methods rewritten,\n C = marked with @Contended annotation,\n R = has been redefined,\n S = is an (App)CDS shared class,\n (if -location is specified, append: 's' (static) or 'd' (dynamic) for AOT cache location)"; + protected: DCmdArgument _verbose; + DCmdArgument _location; public: - static int num_arguments() { return 1; } + static int num_arguments() { return 2; } ClassesDCmd(outputStream* output, bool heap); static const char* name() { return "VM.classes"; } static const char* description() { - return "Print all loaded classes"; + return desc; } static const char* impact() { return "Medium: Depends on number of loaded classes."; diff --git a/src/jdk.jcmd/share/man/jcmd.md b/src/jdk.jcmd/share/man/jcmd.md index 23dfa67d864..a89e0da608e 100644 --- a/src/jdk.jcmd/share/man/jcmd.md +++ b/src/jdk.jcmd/share/man/jcmd.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 2026, 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 @@ -800,19 +800,35 @@ The following commands are available: no default value) `VM.classes` \[*options*\] -: Print all loaded classes +: Print all loaded classes. + + Classes may be annotated with flags: + - `F` = has, or inherits, a non-empty finalize method, + - `f` = has final method, + - `W` = methods rewritten, + - `C` = marked with `@Contended` annotation, + - `R` = has been redefined, + - `S` = is shared class (if `-location` is specified then either 's' (static) or 'd' (dynamic) for AOT cache origin is appended) Impact: Medium: Depends on number of loaded classes. - The following *options* must be specified using either *key* or - *key*`=`*value* syntax. + The following *options* must be specified using either *key* or *key*`=`*value* syntax. *options*: - - `-verbose`: (Optional) Dump the detailed content of a Java class. - Some classes are annotated with flags: `F` = has, or inherits, a non-empty finalize method, - `f` = has final method, `W` = methods rewritten, `C` = marked with `@Contended` annotation, - `R` = has been redefined, `S` = is shared class (BOOLEAN, false) + - `-verbose`: (Optional) Dump the detailed content of a Java class. (BOOLEAN, false) + + - `-location`: (Optional) Print the location of the class file from which the class is loaded (if available). + If provided by its defining ClassLoader, this option will print a URL specifying the location of the + class file (directory, jar or other URL location) from which this class was initially loaded. + + Note: JDK (and other classes) loaded by a ClassLoader that does not provide a location URL to the JVM will omit this field. + + Note: if any classes are loaded from an AOT cache, their location reported is that of the original + URL from which they were loaded at the time of the training run that created the AOT cache. + Additionally the flags will also be annotated to indicate the AOT cache origin (static or dynamic). + + The total number of classes loaded (if any) from either AOT cache (and the associated cache path location) are summarized. `VM.classloader_stats` : Print statistics about all ClassLoaders. diff --git a/test/hotspot/jtreg/runtime/CommandLine/PrintClasses.java b/test/hotspot/jtreg/runtime/CommandLine/PrintClasses.java index e2b0b4ba9f6..e96e5ca5114 100644 --- a/test/hotspot/jtreg/runtime/CommandLine/PrintClasses.java +++ b/test/hotspot/jtreg/runtime/CommandLine/PrintClasses.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, Alibaba Group Holding Limited. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -59,5 +59,9 @@ public class PrintClasses { // Test for previous bug in misc flags printing output.shouldNotContain("##name"); + + pb.command(new PidJcmdExecutor().getCommandLine("VM.classes", "-location")); + output = new OutputAnalyzer(pb.start()); + output.stdoutShouldMatch("^.*(file:/|jar:).*$"); } }