8308903: Print detailed info for Java objects in -Xlog:cds+map

Reviewed-by: stuefe, ccheung
This commit is contained in:
Ioi Lam 2023-07-24 17:56:42 +00:00
parent d8f2e9ae3b
commit 8008e27c55
9 changed files with 585 additions and 41 deletions

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

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

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