8327246: Add a jcmd diagnostic command to list Class origin URLs where possible

This commit is contained in:
Kevin Walls 2026-06-08 15:39:57 +01:00
parent 4ff29cbde4
commit a2bbf228f5
6 changed files with 109 additions and 37 deletions

View File

@ -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) {

View File

@ -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);
};

View File

@ -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);
}

View File

@ -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<bool> _verbose;
DCmdArgument<bool> _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.";

View File

@ -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.

View File

@ -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:).*$");
}
}