8308903: Print detailed info for Java objects in -Xlog:cds+map
Reviewed-by: stuefe, ccheung
This commit is contained in:
parent
d8f2e9ae3b
commit
8008e27c55
src/hotspot/share
cds
oops
test/hotspot/jtreg/runtime/cds
@ -32,6 +32,7 @@
|
||||
#include "cds/metaspaceShared.hpp"
|
||||
#include "cds/regeneratedClasses.hpp"
|
||||
#include "classfile/classLoaderDataShared.hpp"
|
||||
#include "classfile/javaClasses.hpp"
|
||||
#include "classfile/symbolTable.hpp"
|
||||
#include "classfile/systemDictionaryShared.hpp"
|
||||
#include "classfile/vmClasses.hpp"
|
||||
@ -47,6 +48,7 @@
|
||||
#include "oops/objArrayOop.inline.hpp"
|
||||
#include "oops/oopHandle.inline.hpp"
|
||||
#include "runtime/arguments.hpp"
|
||||
#include "runtime/fieldDescriptor.inline.hpp"
|
||||
#include "runtime/globals_extension.hpp"
|
||||
#include "runtime/javaThread.hpp"
|
||||
#include "runtime/sharedRuntime.hpp"
|
||||
@ -965,7 +967,7 @@ class ArchiveBuilder::CDSMapLogger : AllStatic {
|
||||
SourceObjInfo* src_info = src_objs->at(i);
|
||||
address src = src_info->source_addr();
|
||||
address dest = src_info->buffered_addr();
|
||||
log_data(last_obj_base, dest, last_obj_base + buffer_to_runtime_delta());
|
||||
log_as_hex(last_obj_base, dest, last_obj_base + buffer_to_runtime_delta());
|
||||
address runtime_dest = dest + buffer_to_runtime_delta();
|
||||
int bytes = src_info->size_in_bytes();
|
||||
|
||||
@ -1007,12 +1009,12 @@ class ArchiveBuilder::CDSMapLogger : AllStatic {
|
||||
last_obj_end = dest + bytes;
|
||||
}
|
||||
|
||||
log_data(last_obj_base, last_obj_end, last_obj_base + buffer_to_runtime_delta());
|
||||
log_as_hex(last_obj_base, last_obj_end, last_obj_base + buffer_to_runtime_delta());
|
||||
if (last_obj_end < region_end) {
|
||||
log_debug(cds, map)(PTR_FORMAT ": @@ Misc data " SIZE_FORMAT " bytes",
|
||||
p2i(last_obj_end + buffer_to_runtime_delta()),
|
||||
size_t(region_end - last_obj_end));
|
||||
log_data(last_obj_end, region_end, last_obj_end + buffer_to_runtime_delta());
|
||||
log_as_hex(last_obj_end, region_end, last_obj_end + buffer_to_runtime_delta());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1038,44 +1040,167 @@ class ArchiveBuilder::CDSMapLogger : AllStatic {
|
||||
MemRegion r = heap_info->buffer_region();
|
||||
address start = address(r.start());
|
||||
address end = address(r.end());
|
||||
log_region("heap", start, end, to_requested(start));
|
||||
log_region("heap", start, end, ArchiveHeapWriter::buffered_addr_to_requested_addr(start));
|
||||
|
||||
LogStreamHandle(Info, cds, map) st;
|
||||
|
||||
while (start < end) {
|
||||
size_t byte_size;
|
||||
oop original_oop = ArchiveHeapWriter::buffered_addr_to_source_obj(start);
|
||||
if (original_oop != nullptr) {
|
||||
ResourceMark rm;
|
||||
log_info(cds, map)(PTR_FORMAT ": @@ Object %s",
|
||||
p2i(to_requested(start)), original_oop->klass()->external_name());
|
||||
byte_size = original_oop->size() * BytesPerWord;
|
||||
oop source_oop = ArchiveHeapWriter::buffered_addr_to_source_obj(start);
|
||||
address requested_start = ArchiveHeapWriter::buffered_addr_to_requested_addr(start);
|
||||
st.print(PTR_FORMAT ": @@ Object ", p2i(requested_start));
|
||||
|
||||
if (source_oop != nullptr) {
|
||||
// This is a regular oop that got archived.
|
||||
print_oop_with_requested_addr_cr(&st, source_oop, false);
|
||||
byte_size = source_oop->size() * BytesPerWord;
|
||||
} else if (start == ArchiveHeapWriter::buffered_heap_roots_addr()) {
|
||||
// HeapShared::roots() is copied specially so it doesn't exist in
|
||||
// HeapShared::OriginalObjectTable. See HeapShared::copy_roots().
|
||||
log_info(cds, map)(PTR_FORMAT ": @@ Object HeapShared::roots (ObjArray)",
|
||||
p2i(to_requested(start)));
|
||||
// HeapShared::roots() is copied specially, so it doesn't exist in
|
||||
// ArchiveHeapWriter::BufferOffsetToSourceObjectTable.
|
||||
// See ArchiveHeapWriter::copy_roots_to_buffer().
|
||||
st.print_cr("HeapShared::roots[%d]", HeapShared::pending_roots()->length());
|
||||
byte_size = ArchiveHeapWriter::heap_roots_word_size() * BytesPerWord;
|
||||
} else if ((byte_size = ArchiveHeapWriter::get_filler_size_at(start)) > 0) {
|
||||
// We have a filler oop, which also does not exist in BufferOffsetToSourceObjectTable.
|
||||
st.print_cr("filler " SIZE_FORMAT " bytes", byte_size);
|
||||
} else {
|
||||
// We have reached the end of the region, but have some unused space
|
||||
// at the end.
|
||||
log_info(cds, map)(PTR_FORMAT ": @@ Unused heap space " SIZE_FORMAT " bytes",
|
||||
p2i(to_requested(start)), size_t(end - start));
|
||||
log_data(start, end, to_requested(start), /*is_heap=*/true);
|
||||
break;
|
||||
ShouldNotReachHere();
|
||||
}
|
||||
|
||||
address oop_end = start + byte_size;
|
||||
log_data(start, oop_end, to_requested(start), /*is_heap=*/true);
|
||||
log_as_hex(start, oop_end, requested_start, /*is_heap=*/true);
|
||||
|
||||
if (source_oop != nullptr) {
|
||||
log_oop_details(heap_info, source_oop);
|
||||
} else if (start == ArchiveHeapWriter::buffered_heap_roots_addr()) {
|
||||
log_heap_roots();
|
||||
}
|
||||
start = oop_end;
|
||||
}
|
||||
}
|
||||
|
||||
static address to_requested(address p) {
|
||||
return ArchiveHeapWriter::buffered_addr_to_requested_addr(p);
|
||||
// ArchivedFieldPrinter is used to print the fields of archived objects. We can't
|
||||
// use _source_obj->print_on(), because we want to print the oop fields
|
||||
// in _source_obj with their requested addresses using print_oop_with_requested_addr_cr().
|
||||
class ArchivedFieldPrinter : public FieldClosure {
|
||||
ArchiveHeapInfo* _heap_info;
|
||||
outputStream* _st;
|
||||
oop _source_obj;
|
||||
public:
|
||||
ArchivedFieldPrinter(ArchiveHeapInfo* heap_info, outputStream* st, oop src_obj) :
|
||||
_heap_info(heap_info), _st(st), _source_obj(src_obj) {}
|
||||
|
||||
void do_field(fieldDescriptor* fd) {
|
||||
_st->print(" - ");
|
||||
BasicType ft = fd->field_type();
|
||||
switch (ft) {
|
||||
case T_ARRAY:
|
||||
case T_OBJECT:
|
||||
fd->print_on(_st); // print just the name and offset
|
||||
print_oop_with_requested_addr_cr(_st, _source_obj->obj_field(fd->offset()));
|
||||
break;
|
||||
default:
|
||||
if (ArchiveHeapWriter::is_marked_as_native_pointer(_heap_info, _source_obj, fd->offset())) {
|
||||
print_as_native_pointer(fd);
|
||||
} else {
|
||||
fd->print_on_for(_st, _source_obj); // name, offset, value
|
||||
_st->cr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void print_as_native_pointer(fieldDescriptor* fd) {
|
||||
LP64_ONLY(assert(fd->field_type() == T_LONG, "must be"));
|
||||
NOT_LP64 (assert(fd->field_type() == T_INT, "must be"));
|
||||
|
||||
// We have a field that looks like an integer, but it's actually a pointer to a MetaspaceObj.
|
||||
address source_native_ptr = (address)
|
||||
LP64_ONLY(_source_obj->long_field(fd->offset()))
|
||||
NOT_LP64( _source_obj->int_field (fd->offset()));
|
||||
ArchiveBuilder* builder = ArchiveBuilder::current();
|
||||
|
||||
// The value of the native pointer at runtime.
|
||||
address requested_native_ptr = builder->to_requested(builder->get_buffered_addr(source_native_ptr));
|
||||
|
||||
// The address of _source_obj at runtime
|
||||
oop requested_obj = ArchiveHeapWriter::source_obj_to_requested_obj(_source_obj);
|
||||
// The address of this field in the requested space
|
||||
address requested_field_addr = cast_from_oop<address>(requested_obj) + fd->offset();
|
||||
|
||||
fd->print_on(_st);
|
||||
_st->print_cr(PTR_FORMAT " (marked metadata pointer @" PTR_FORMAT " )",
|
||||
p2i(requested_native_ptr), p2i(requested_field_addr));
|
||||
}
|
||||
};
|
||||
|
||||
// Print the fields of instanceOops, or the elements of arrayOops
|
||||
static void log_oop_details(ArchiveHeapInfo* heap_info, oop source_oop) {
|
||||
LogStreamHandle(Trace, cds, map, oops) st;
|
||||
if (st.is_enabled()) {
|
||||
Klass* source_klass = source_oop->klass();
|
||||
ArchiveBuilder* builder = ArchiveBuilder::current();
|
||||
Klass* requested_klass = builder->to_requested(builder->get_buffered_addr(source_klass));
|
||||
|
||||
st.print(" - klass: ");
|
||||
source_klass->print_value_on(&st);
|
||||
st.print(" " PTR_FORMAT, p2i(requested_klass));
|
||||
st.cr();
|
||||
|
||||
if (source_oop->is_typeArray()) {
|
||||
TypeArrayKlass::cast(source_klass)->oop_print_elements_on(typeArrayOop(source_oop), &st);
|
||||
} else if (source_oop->is_objArray()) {
|
||||
objArrayOop source_obj_array = objArrayOop(source_oop);
|
||||
for (int i = 0; i < source_obj_array->length(); i++) {
|
||||
st.print(" -%4d: ", i);
|
||||
print_oop_with_requested_addr_cr(&st, source_obj_array->obj_at(i));
|
||||
}
|
||||
} else {
|
||||
st.print_cr(" - fields (" SIZE_FORMAT " words):", source_oop->size());
|
||||
ArchivedFieldPrinter print_field(heap_info, &st, source_oop);
|
||||
InstanceKlass::cast(source_klass)->print_nonstatic_fields(&print_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void log_heap_roots() {
|
||||
LogStreamHandle(Trace, cds, map, oops) st;
|
||||
if (st.is_enabled()) {
|
||||
for (int i = 0; i < HeapShared::pending_roots()->length(); i++) {
|
||||
st.print("roots[%4d]: ", i);
|
||||
print_oop_with_requested_addr_cr(&st, HeapShared::pending_roots()->at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The output looks like this. The first number is the requested address. The second number is
|
||||
// the narrowOop version of the requested address.
|
||||
// 0x00000007ffc7e840 (0xfff8fd08) java.lang.Class
|
||||
// 0x00000007ffc000f8 (0xfff8001f) [B length: 11
|
||||
static void print_oop_with_requested_addr_cr(outputStream* st, oop source_oop, bool print_addr = true) {
|
||||
if (source_oop == nullptr) {
|
||||
st->print_cr("null");
|
||||
} else {
|
||||
ResourceMark rm;
|
||||
oop requested_obj = ArchiveHeapWriter::source_obj_to_requested_obj(source_oop);
|
||||
if (print_addr) {
|
||||
st->print(PTR_FORMAT " ", p2i(requested_obj));
|
||||
}
|
||||
if (UseCompressedOops) {
|
||||
st->print("(0x%08x) ", CompressedOops::narrow_oop_value(requested_obj));
|
||||
}
|
||||
if (source_oop->is_array()) {
|
||||
int array_len = arrayOop(source_oop)->length();
|
||||
st->print_cr("%s length: %d", source_oop->klass()->external_name(), array_len);
|
||||
} else {
|
||||
st->print_cr("%s", source_oop->klass()->external_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // INCLUDE_CDS_JAVA_HEAP
|
||||
|
||||
// Log all the data [base...top). Pretend that the base address
|
||||
// will be mapped to requested_base at run-time.
|
||||
static void log_data(address base, address top, address requested_base, bool is_heap = false) {
|
||||
static void log_as_hex(address base, address top, address requested_base, bool is_heap = false) {
|
||||
assert(top >= base, "must be");
|
||||
|
||||
LogStreamHandle(Trace, cds, map) lsh;
|
||||
@ -1107,7 +1232,7 @@ public:
|
||||
address header_end = header + mapinfo->header()->header_size();
|
||||
log_region("header", header, header_end, 0);
|
||||
log_header(mapinfo);
|
||||
log_data(header, header_end, 0);
|
||||
log_as_hex(header, header_end, 0);
|
||||
|
||||
DumpRegion* rw_region = &builder->_rw_region;
|
||||
DumpRegion* ro_region = &builder->_ro_region;
|
||||
@ -1117,7 +1242,7 @@ public:
|
||||
|
||||
address bitmap_end = address(bitmap + bitmap_size_in_bytes);
|
||||
log_region("bitmap", address(bitmap), bitmap_end, 0);
|
||||
log_data((address)bitmap, bitmap_end, 0);
|
||||
log_as_hex((address)bitmap, bitmap_end, 0);
|
||||
|
||||
#if INCLUDE_CDS_JAVA_HEAP
|
||||
if (heap_info->is_used()) {
|
||||
|
@ -64,12 +64,19 @@ GrowableArrayCHeap<oop, mtClassShared>* ArchiveHeapWriter::_source_objs;
|
||||
ArchiveHeapWriter::BufferOffsetToSourceObjectTable*
|
||||
ArchiveHeapWriter::_buffer_offset_to_source_obj_table = nullptr;
|
||||
|
||||
|
||||
typedef ResourceHashtable<address, size_t,
|
||||
127, // prime number
|
||||
AnyObj::C_HEAP,
|
||||
mtClassShared> FillersTable;
|
||||
static FillersTable* _fillers;
|
||||
|
||||
void ArchiveHeapWriter::init() {
|
||||
if (HeapShared::can_write()) {
|
||||
Universe::heap()->collect(GCCause::_java_lang_system_gc);
|
||||
|
||||
_buffer_offset_to_source_obj_table = new BufferOffsetToSourceObjectTable();
|
||||
|
||||
_fillers = new FillersTable();
|
||||
_requested_bottom = nullptr;
|
||||
_requested_top = nullptr;
|
||||
|
||||
@ -255,7 +262,7 @@ int ArchiveHeapWriter::filler_array_length(size_t fill_bytes) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ArchiveHeapWriter::init_filler_array_at_buffer_top(int array_length, size_t fill_bytes) {
|
||||
HeapWord* ArchiveHeapWriter::init_filler_array_at_buffer_top(int array_length, size_t fill_bytes) {
|
||||
assert(UseCompressedClassPointers, "Archived heap only supported for compressed klasses");
|
||||
Klass* oak = Universe::objectArrayKlassObj(); // already relocated to point to archived klass
|
||||
HeapWord* mem = offset_to_buffered_address<HeapWord*>(_buffer_used);
|
||||
@ -264,6 +271,7 @@ void ArchiveHeapWriter::init_filler_array_at_buffer_top(int array_length, size_t
|
||||
narrowKlass nk = ArchiveBuilder::current()->get_requested_narrow_klass(oak);
|
||||
cast_to_oop(mem)->set_narrow_klass(nk);
|
||||
arrayOopDesc::set_length(mem, array_length);
|
||||
return mem;
|
||||
}
|
||||
|
||||
void ArchiveHeapWriter::maybe_fill_gc_region_gap(size_t required_byte_size) {
|
||||
@ -293,9 +301,19 @@ void ArchiveHeapWriter::maybe_fill_gc_region_gap(size_t required_byte_size) {
|
||||
int array_length = filler_array_length(fill_bytes);
|
||||
log_info(cds, heap)("Inserting filler obj array of %d elements (" SIZE_FORMAT " bytes total) @ buffer offset " SIZE_FORMAT,
|
||||
array_length, fill_bytes, _buffer_used);
|
||||
init_filler_array_at_buffer_top(array_length, fill_bytes);
|
||||
|
||||
HeapWord* filler = init_filler_array_at_buffer_top(array_length, fill_bytes);
|
||||
_buffer_used = filler_end;
|
||||
_fillers->put((address)filler, fill_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
size_t ArchiveHeapWriter::get_filler_size_at(address buffered_addr) {
|
||||
size_t* p = _fillers->get(buffered_addr);
|
||||
if (p != nullptr) {
|
||||
assert(*p > 0, "filler must be larger than zero bytes");
|
||||
return *p;
|
||||
} else {
|
||||
return 0; // buffered_addr is not a filler
|
||||
}
|
||||
}
|
||||
|
||||
@ -514,6 +532,20 @@ void ArchiveHeapWriter::mark_native_pointer(oop src_obj, int field_offset) {
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have a jlong/jint field that's actually a pointer to a MetaspaceObj?
|
||||
bool ArchiveHeapWriter::is_marked_as_native_pointer(ArchiveHeapInfo* heap_info, oop src_obj, int field_offset) {
|
||||
HeapShared::CachedOopInfo* p = HeapShared::archived_object_cache()->get(src_obj);
|
||||
assert(p != nullptr, "must be");
|
||||
|
||||
// requested_field_addr = the address of this field in the requested space
|
||||
oop requested_obj = requested_obj_from_buffer_offset(p->buffer_offset());
|
||||
Metadata** requested_field_addr = (Metadata**)(cast_from_oop<address>(requested_obj) + field_offset);
|
||||
assert((Metadata**)_requested_bottom <= requested_field_addr && requested_field_addr < (Metadata**) _requested_top, "range check");
|
||||
|
||||
BitMap::idx_t idx = requested_field_addr - (Metadata**) _requested_bottom;
|
||||
return (idx < heap_info->ptrmap()->size()) && (heap_info->ptrmap()->at(idx) == true);
|
||||
}
|
||||
|
||||
void ArchiveHeapWriter::compute_ptrmap(ArchiveHeapInfo* heap_info) {
|
||||
int num_non_null_ptrs = 0;
|
||||
Metadata** bottom = (Metadata**) _requested_bottom;
|
||||
|
@ -188,7 +188,7 @@ private:
|
||||
static void maybe_fill_gc_region_gap(size_t required_byte_size);
|
||||
static size_t filler_array_byte_size(int length);
|
||||
static int filler_array_length(size_t fill_bytes);
|
||||
static void init_filler_array_at_buffer_top(int array_length, size_t fill_bytes);
|
||||
static HeapWord* init_filler_array_at_buffer_top(int array_length, size_t fill_bytes);
|
||||
|
||||
static void set_requested_address(ArchiveHeapInfo* info);
|
||||
static void relocate_embedded_oops(GrowableArrayCHeap<oop, mtClassShared>* roots, ArchiveHeapInfo* info);
|
||||
@ -225,8 +225,10 @@ public:
|
||||
static size_t heap_roots_word_size() {
|
||||
return _heap_roots_word_size;
|
||||
}
|
||||
static size_t get_filler_size_at(address buffered_addr);
|
||||
|
||||
static void mark_native_pointer(oop src_obj, int offset);
|
||||
static bool is_marked_as_native_pointer(ArchiveHeapInfo* heap_info, oop src_obj, int field_offset);
|
||||
static oop source_obj_to_requested_obj(oop src_obj);
|
||||
static oop buffered_addr_to_source_obj(address buffered_addr);
|
||||
static address buffered_addr_to_requested_addr(address buffered_addr);
|
||||
|
@ -381,6 +381,7 @@ private:
|
||||
// Dump-time only. Returns the index of the root, which can be used at run time to read
|
||||
// the root using get_root(index, ...).
|
||||
static int append_root(oop obj);
|
||||
static GrowableArrayCHeap<oop, mtClassShared>* pending_roots() { return _pending_roots; }
|
||||
|
||||
// Dump-time and runtime
|
||||
static objArrayOop roots();
|
||||
|
@ -276,8 +276,6 @@ void TypeArrayKlass::print_value_on(outputStream* st) const {
|
||||
st->print("}");
|
||||
}
|
||||
|
||||
#ifndef PRODUCT
|
||||
|
||||
static void print_boolean_array(typeArrayOop ta, int print_len, outputStream* st) {
|
||||
for (int index = 0; index < print_len; index++) {
|
||||
st->print_cr(" - %3d: %s", index, (ta->bool_at(index) == 0) ? "false" : "true");
|
||||
@ -341,7 +339,10 @@ static void print_long_array(typeArrayOop ta, int print_len, outputStream* st) {
|
||||
|
||||
void TypeArrayKlass::oop_print_on(oop obj, outputStream* st) {
|
||||
ArrayKlass::oop_print_on(obj, st);
|
||||
typeArrayOop ta = typeArrayOop(obj);
|
||||
oop_print_elements_on(typeArrayOop(obj), st);
|
||||
}
|
||||
|
||||
void TypeArrayKlass::oop_print_elements_on(typeArrayOop ta, outputStream* st) {
|
||||
int print_len = MIN2((intx) ta->length(), MaxElementPrintSize);
|
||||
switch (element_type()) {
|
||||
case T_BOOLEAN: print_boolean_array(ta, print_len, st); break;
|
||||
@ -360,8 +361,6 @@ void TypeArrayKlass::oop_print_on(oop obj, outputStream* st) {
|
||||
}
|
||||
}
|
||||
|
||||
#endif // PRODUCT
|
||||
|
||||
const char* TypeArrayKlass::internal_name() const {
|
||||
return Klass::external_name();
|
||||
}
|
||||
|
@ -123,10 +123,8 @@ class TypeArrayKlass : public ArrayKlass {
|
||||
|
||||
public:
|
||||
// Printing
|
||||
#ifndef PRODUCT
|
||||
void oop_print_on(oop obj, outputStream* st);
|
||||
#endif
|
||||
|
||||
void oop_print_elements_on(typeArrayOop ta, outputStream* st);
|
||||
void print_on(outputStream* st) const;
|
||||
void print_value_on(outputStream* st) const;
|
||||
|
||||
|
301
test/hotspot/jtreg/runtime/cds/CDSMapReader.java
Normal file
301
test/hotspot/jtreg/runtime/cds/CDSMapReader.java
Normal file
@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
// This is a simple parser for parsing the output of
|
||||
//
|
||||
// java -Xshare:dump -Xlog:cds+map=debug,cds+map+oops=trace:file=cds.map:none:filesize=0
|
||||
//
|
||||
// Currently it just check the output related to JDK-8308903.
|
||||
// I.e., each oop fields in the HeapObjects must point to a valid HeapObject.
|
||||
//
|
||||
// It can be extended to check for the other parts of the map file, or perform
|
||||
// more analysis on the HeapObjects.
|
||||
public class CDSMapReader {
|
||||
public static class MapFile {
|
||||
ArrayList<HeapObject> heapObjects = new ArrayList<>();
|
||||
HashMap<Long, HeapObject> oopToObject = new HashMap<>();
|
||||
HashMap<Long, HeapObject> narrowOopToObject = new HashMap<>();
|
||||
|
||||
void add(HeapObject heapObject) {
|
||||
heapObjects.add(heapObject);
|
||||
oopToObject.put(heapObject.address.oop, heapObject);
|
||||
if (heapObject.address.narrowOop != 0) {
|
||||
narrowOopToObject.put(heapObject.address.narrowOop, heapObject);
|
||||
}
|
||||
}
|
||||
|
||||
public int heapObjectCount() {
|
||||
return heapObjects.size();
|
||||
}
|
||||
}
|
||||
|
||||
public static class HeapAddress {
|
||||
long oop;
|
||||
long narrowOop;
|
||||
|
||||
HeapAddress(String oopStr, String narrowOopStr) {
|
||||
oop = Long.parseUnsignedLong(oopStr, 16);
|
||||
if (narrowOopStr != null) {
|
||||
narrowOop = Long.parseUnsignedLong(narrowOopStr, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Klass {
|
||||
long address;
|
||||
String name;
|
||||
|
||||
static Klass getKlass(String name, String addr) {
|
||||
// TODO: look up from a table of known Klasses
|
||||
Klass k = new Klass();
|
||||
k.name = name;
|
||||
k.address = Long.parseUnsignedLong(addr, 16);
|
||||
return k;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HeapObject {
|
||||
HeapAddress address;
|
||||
ArrayList<Field> fields;
|
||||
String className;
|
||||
Klass klass;
|
||||
|
||||
HeapObject(String className, String oop, String narrowOop) {
|
||||
this.className = className;
|
||||
address = new HeapAddress(oop, narrowOop);
|
||||
}
|
||||
|
||||
void setKlass(String klassName, String address) {
|
||||
klass = Klass.getKlass(klassName, address);
|
||||
}
|
||||
|
||||
void addOopField(String name, String offset, String oopStr, String narrowOopStr) {
|
||||
if (fields == null) {
|
||||
fields = new ArrayList<Field>();
|
||||
}
|
||||
fields.add(new Field(name, offset, oopStr, narrowOopStr));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Field {
|
||||
String name;
|
||||
int offset;
|
||||
HeapAddress referentAddress; // non-null iff this is an object field
|
||||
int lineCount;
|
||||
|
||||
Field(String name, String offset, String oopStr, String narrowOopStr) {
|
||||
this.name = name;
|
||||
this.offset = Integer.parseInt(offset);
|
||||
this.referentAddress = new HeapAddress(oopStr, narrowOopStr);
|
||||
this.lineCount = CDSMapReader.lineCount;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x00000007ffc00000: 4a5b8701 00000063 00010290 00000000 00010100 fff80003
|
||||
static Pattern rawDataPattern = Pattern.compile("^0x([0-9a-f]+): *( [0-9a-f]+)+ *$");
|
||||
|
||||
// (one address)
|
||||
// 0x00000007ffc00000: @@ Object java.lang.String
|
||||
static Pattern objPattern1 = Pattern.compile("^0x([0-9a-f]+): @@ Object (.*)");
|
||||
|
||||
// (two addresses)
|
||||
// 0x00000007ffc00000: @@ Object (0xfff80000) java.lang.String
|
||||
static Pattern objPattern2 = Pattern.compile("^0x([0-9a-f]+): @@ Object [(]0x([0-9a-f]+)[)] (.*)");
|
||||
|
||||
// - klass: 'java/lang/String' 0x0000000800010290
|
||||
static Pattern instanceObjKlassPattern = Pattern.compile("^ - klass: '([^']+)' 0x([0-9a-f]+)");
|
||||
|
||||
// - klass: {type array byte} 0x00000008000024c8
|
||||
static Pattern typeArrayKlassPattern = Pattern.compile("^ - klass: [{]type array ([a-z]+)[}] 0x([0-9a-f]+)");
|
||||
|
||||
// - klass: 'java/lang/Object'[] 0x00000008000013e0
|
||||
static Pattern objArrayKlassPattern = Pattern.compile("^ - klass: ('[^']+'(\\[\\])+) 0x([0-9a-f]+)");
|
||||
|
||||
// - fields (3 words):
|
||||
static Pattern fieldsWordsPattern = Pattern.compile("^ - fields [(]([0-9]+) words[)]:$");
|
||||
|
||||
// (one address)
|
||||
// - final 'key' 'Ljava/lang/Object;' @16 0x00000007ffc68260 java.lang.String
|
||||
static Pattern oopFieldPattern1 = Pattern.compile(" - [^']* '([^']+)'.*@([0-9]+) 0x([0-9a-f]+) (.*)");
|
||||
|
||||
// (two addresses)
|
||||
// - final 'key' 'Ljava/lang/Object;' @16 0x00000007ffc68260 (0xfff8d04c) java.lang.String
|
||||
static Pattern oopFieldPattern2 = Pattern.compile(" - [^']* '([^']+)'.*@([0-9]+) 0x([0-9a-f]+) [(]0x([0-9a-f]+)[)] (.*)");
|
||||
|
||||
private static Matcher match(String line, Pattern pattern) {
|
||||
Matcher m = pattern.matcher(line);
|
||||
if (m.find()) {
|
||||
return m;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseHeapObject(String className, String oop, String narrowOop) throws IOException {
|
||||
HeapObject heapObject = parseHeapObjectImpl(className, oop, narrowOop);
|
||||
mapFile.add(heapObject);
|
||||
}
|
||||
|
||||
private static HeapObject parseHeapObjectImpl(String className, String oop, String narrowOop) throws IOException {
|
||||
HeapObject heapObject = new HeapObject(className, oop, narrowOop);
|
||||
Matcher m;
|
||||
|
||||
nextLine();
|
||||
while (line != null && match(line, rawDataPattern) != null) { // skip raw data
|
||||
nextLine();
|
||||
}
|
||||
|
||||
if (line == null || !line.startsWith(" - ")) {
|
||||
return heapObject;
|
||||
}
|
||||
|
||||
if ((m = match(line, instanceObjKlassPattern)) != null) {
|
||||
heapObject.setKlass(m.group(1), m.group(2));
|
||||
nextLine();
|
||||
if ((m = match(line, fieldsWordsPattern)) == null) {
|
||||
throw new RuntimeException("Expected field size info");
|
||||
}
|
||||
// TODO: read all the array elements
|
||||
while (true) {
|
||||
nextLine();
|
||||
if (line == null || !line.startsWith(" - ")) {
|
||||
return heapObject;
|
||||
}
|
||||
if (!line.contains("marked metadata pointer")) {
|
||||
if ((m = match(line, oopFieldPattern2)) != null) {
|
||||
heapObject.addOopField(m.group(1), m.group(2), m.group(3), m.group(4));
|
||||
} else if ((m = match(line, oopFieldPattern1)) != null) {
|
||||
heapObject.addOopField(m.group(1), m.group(2), m.group(3), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ((m = match(line, typeArrayKlassPattern)) != null) {
|
||||
heapObject.setKlass(m.group(1), m.group(2));
|
||||
// TODO: read all the array elements
|
||||
while (true) {
|
||||
nextLine();
|
||||
if (line == null || !line.startsWith(" - ")) {
|
||||
return heapObject;
|
||||
}
|
||||
}
|
||||
} else if ((m = match(line, objArrayKlassPattern)) != null) {
|
||||
heapObject.setKlass(m.group(1), m.group(3));
|
||||
// TODO: read all the array elements
|
||||
while (true) {
|
||||
nextLine();
|
||||
if (line == null || !line.startsWith(" - ")) {
|
||||
return heapObject;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Expected klass info");
|
||||
}
|
||||
}
|
||||
|
||||
static MapFile mapFile;
|
||||
static BufferedReader reader;
|
||||
static String line = null; // current line being parsed
|
||||
static int lineCount = 0;
|
||||
static String nextLine() throws IOException {
|
||||
line = reader.readLine();
|
||||
++ lineCount;
|
||||
return line;
|
||||
}
|
||||
|
||||
public static MapFile read(String fileName) {
|
||||
mapFile = new MapFile();
|
||||
|
||||
try (BufferedReader r = new BufferedReader(new FileReader(fileName))) {
|
||||
reader = r;
|
||||
nextLine();
|
||||
|
||||
Matcher m;
|
||||
while (line != null) {
|
||||
if ((m = match(line, objPattern2)) != null) {
|
||||
parseHeapObject(m.group(3), m.group(1), m.group(2));
|
||||
} else if ((m = match(line, objPattern1)) != null) {
|
||||
parseHeapObject(m.group(2), m.group(1), null);
|
||||
} else {
|
||||
nextLine();
|
||||
}
|
||||
}
|
||||
return mapFile;
|
||||
} catch (Throwable t) {
|
||||
System.out.println("Error parsing line " + lineCount + ": " + line);
|
||||
throw new RuntimeException(t);
|
||||
} finally {
|
||||
System.out.println("Parsed " + lineCount + " lines in " + fileName);
|
||||
System.out.println("Found " + mapFile.heapObjectCount() + " heap objects");
|
||||
mapFile = null;
|
||||
reader = null;
|
||||
line = null;
|
||||
lineCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void mustContain(HashMap<Long, HeapObject> allObjects, Field field, long pointer, boolean isNarrow) {
|
||||
if (allObjects.get(pointer) == null) {
|
||||
throw new RuntimeException((isNarrow ? "narrowOop" : "oop") + " pointer 0x" + Long.toHexString(pointer) +
|
||||
" on line " + field.lineCount + " doesn't point to a valid heap object");
|
||||
}
|
||||
}
|
||||
|
||||
// Check that each oop fields in the HeapObjects must point to a valid HeapObject.
|
||||
public static int validate(MapFile mapFile) {
|
||||
int count = 0;
|
||||
for (HeapObject heapObject : mapFile.heapObjects) {
|
||||
if (heapObject.fields != null) {
|
||||
for (Field field : heapObject.fields) {
|
||||
HeapAddress referentAddress = field.referentAddress;
|
||||
long oop = referentAddress.oop;
|
||||
long narrowOop = referentAddress.narrowOop;
|
||||
// Is this test actually doing something?
|
||||
// To see how an invalidate pointer may be found, change oop in the
|
||||
// following line to oop+1
|
||||
mustContain(mapFile.oopToObject, field, oop, false);
|
||||
count ++;
|
||||
if (narrowOop != 0) {
|
||||
mustContain(mapFile.narrowOopToObject, field, narrowOop, true);
|
||||
count ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("Checked " + count + " oop field references");
|
||||
return count;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
MapFile mapFile = read(args[0]);
|
||||
validate(mapFile);
|
||||
}
|
||||
}
|
86
test/hotspot/jtreg/runtime/cds/CDSMapTest.java
Normal file
86
test/hotspot/jtreg/runtime/cds/CDSMapTest.java
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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
|
||||
* @bug 8308903
|
||||
* @summary Test the contents of -Xlog:cds+map
|
||||
* @requires vm.cds
|
||||
* @library /test/lib
|
||||
* @run driver CDSMapTest
|
||||
*/
|
||||
|
||||
import jdk.test.lib.cds.CDSOptions;
|
||||
import jdk.test.lib.cds.CDSTestUtils;
|
||||
import jdk.test.lib.Platform;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CDSMapTest {
|
||||
public static void main(String[] args) throws Exception {
|
||||
doTest(false);
|
||||
|
||||
if (Platform.is64bit()) {
|
||||
// There's no oop/klass compression on 32-bit.
|
||||
doTest(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void doTest(boolean compressed) throws Exception {
|
||||
ArrayList<String> dumpArgs = new ArrayList<>();
|
||||
|
||||
// Use the same heap size as make/Images.gmk
|
||||
dumpArgs.add("-Xmx128M");
|
||||
|
||||
if (Platform.is64bit()) {
|
||||
// These options are available only on 64-bit.
|
||||
String sign = (compressed) ? "+" : "-";
|
||||
dumpArgs.add("-XX:" + sign + "UseCompressedOops");
|
||||
}
|
||||
|
||||
dump(dumpArgs);
|
||||
}
|
||||
|
||||
static int id = 0;
|
||||
static void dump(ArrayList<String> args, String... more) throws Exception {
|
||||
String logName = "SharedArchiveFile" + (id++);
|
||||
String archiveName = logName + ".jsa";
|
||||
String mapName = logName + ".map";
|
||||
CDSOptions opts = (new CDSOptions())
|
||||
.addPrefix("-Xlog:cds=debug")
|
||||
.addPrefix("-Xlog:cds+map=debug,cds+map+oops=trace:file=" + mapName + ":none:filesize=0")
|
||||
.setArchiveName(archiveName)
|
||||
.addSuffix(args)
|
||||
.addSuffix(more);
|
||||
CDSTestUtils.createArchiveAndCheck(opts);
|
||||
|
||||
CDSMapReader.MapFile mapFile = CDSMapReader.read(mapName);
|
||||
int oopFieldCount = CDSMapReader.validate(mapFile);
|
||||
if (mapFile.heapObjectCount() > 0 && oopFieldCount < 10000) {
|
||||
// heapObjectCount() may be zero if the selected GC doesn't support heap object archiving.
|
||||
throw new RuntimeException("CDS map file seems incorrect: " + mapFile.heapObjectCount() +
|
||||
" objects but only " + oopFieldCount + " oop field references");
|
||||
}
|
||||
}
|
||||
}
|
@ -80,7 +80,7 @@ public class DeterministicDump {
|
||||
String mapName = logName + ".map";
|
||||
CDSOptions opts = (new CDSOptions())
|
||||
.addPrefix("-Xlog:cds=debug")
|
||||
.addPrefix("-Xlog:cds+map=trace:file=" + mapName + ":none:filesize=0")
|
||||
.addPrefix("-Xlog:cds+map*=trace:file=" + mapName + ":none:filesize=0")
|
||||
.setArchiveName(archiveName)
|
||||
.addSuffix(args)
|
||||
.addSuffix(more);
|
||||
|
Loading…
x
Reference in New Issue
Block a user