From 422ef9f1e4599c93a29bc61786306d3e20ebf9d2 Mon Sep 17 00:00:00 2001 From: Chuansheng Lu Date: Mon, 11 Mar 2019 09:41:56 +0800 Subject: [PATCH] [RAS] Mini-heap dump support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: - Port D336179 mini-heapdump support to Dragonwell - Enable HotSpot to skip dumping content of primitive type arrays Test Plan: hotspot/test/serviceability, jdk/test/ras Reviewers: 井桐 Reviewed By: 井桐 Differential Revision: https://aone.alibaba-inc.com/code/D849312 --- src/share/vm/services/attachListener.cpp | 21 ++- src/share/vm/services/diagnosticCommand.cpp | 13 +- src/share/vm/services/diagnosticCommand.hpp | 3 +- src/share/vm/services/heapDumper.cpp | 35 +++-- src/share/vm/services/heapDumper.hpp | 13 +- .../sa/jmap-minidump/TestMiniDump.sh | 127 ++++++++++++++++++ 6 files changed, 194 insertions(+), 18 deletions(-) create mode 100644 test/serviceability/sa/jmap-minidump/TestMiniDump.sh diff --git a/src/share/vm/services/attachListener.cpp b/src/share/vm/services/attachListener.cpp index 59b2f5483..0029143d6 100644 --- a/src/share/vm/services/attachListener.cpp +++ b/src/share/vm/services/attachListener.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2019, 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 @@ -173,6 +173,7 @@ static jint jcmd(AttachOperation* op, outputStream* out) { // Input arguments :- // arg0: Name of the dump file // arg1: "-live" or "-all" +// arg2: "-mini" or not exist jint dump_heap(AttachOperation* op, outputStream* out) { const char* path = op->arg(0); if (path == NULL || path[0] == '\0') { @@ -188,13 +189,27 @@ jint dump_heap(AttachOperation* op, outputStream* out) { live_objects_only = strcmp(arg1, "-live") == 0; } + bool mini_heap_dump = false; + const char* arg2 = op->arg(2); + if (arg2 != NULL && (strlen(arg2) > 0)) { + if (strcmp(arg2, "-mini") != 0) { + out->print_cr("Invalid argument to dumpheap operation: %s", arg2); + return JNI_ERR; + } + mini_heap_dump = true; + } + // Request a full GC before heap dump if live_objects_only = true // This helps reduces the amount of unreachable objects in the dump // and makes it easier to browse. - HeapDumper dumper(live_objects_only /* request GC */); + HeapDumper dumper(live_objects_only /* request GC */, mini_heap_dump); int res = dumper.dump(op->arg(0)); if (res == 0) { - out->print_cr("Heap dump file created"); + if (mini_heap_dump) { + out->print_cr("Mini-heap dump file created"); + } else { + out->print_cr("Heap dump file created"); + } } else { // heap dump failed ResourceMark rm; diff --git a/src/share/vm/services/diagnosticCommand.cpp b/src/share/vm/services/diagnosticCommand.cpp index ede8db156..80afddaba 100644 --- a/src/share/vm/services/diagnosticCommand.cpp +++ b/src/share/vm/services/diagnosticCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2019, 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 @@ -339,8 +339,11 @@ HeapDumpDCmd::HeapDumpDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), _filename("filename","Name of the dump file", "STRING",true), _all("-all", "Dump all objects, including unreachable objects", + "BOOLEAN", false, "false"), + _mini_dump("-mini", "Use mini-dump format", "BOOLEAN", false, "false") { _dcmdparser.add_dcmd_option(&_all); + _dcmdparser.add_dcmd_option(&_mini_dump); _dcmdparser.add_dcmd_argument(&_filename); } @@ -348,10 +351,14 @@ void HeapDumpDCmd::execute(DCmdSource source, TRAPS) { // Request a full GC before heap dump if _all is false // This helps reduces the amount of unreachable objects in the dump // and makes it easier to browse. - HeapDumper dumper(!_all.value() /* request GC if _all is false*/); + HeapDumper dumper(!_all.value() /* request GC if _all is false*/, _mini_dump.value()); int res = dumper.dump(_filename.value()); if (res == 0) { - output()->print_cr("Heap dump file created"); + if (_mini_dump.value()) { + output()->print_cr("Mini heap dump file created"); + } else { + output()->print_cr("Heap dump file created"); + } } else { // heap dump failed ResourceMark rm; diff --git a/src/share/vm/services/diagnosticCommand.hpp b/src/share/vm/services/diagnosticCommand.hpp index b1fb57e53..0d047589d 100644 --- a/src/share/vm/services/diagnosticCommand.hpp +++ b/src/share/vm/services/diagnosticCommand.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2019, 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 @@ -245,6 +245,7 @@ class HeapDumpDCmd : public DCmdWithParser { protected: DCmdArgument _filename; DCmdArgument _all; + DCmdArgument _mini_dump; public: HeapDumpDCmd(outputStream* output, bool heap); static const char* name() { diff --git a/src/share/vm/services/heapDumper.cpp b/src/share/vm/services/heapDumper.cpp index e5de80eae..3eb0fce4a 100644 --- a/src/share/vm/services/heapDumper.cpp +++ b/src/share/vm/services/heapDumper.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2019, 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 @@ -638,7 +638,7 @@ class DumperSupport : AllStatic { // creates HPROF_GC_OBJ_ARRAY_DUMP record for the given object array static void dump_object_array(DumpWriter* writer, objArrayOop array); // creates HPROF_GC_PRIM_ARRAY_DUMP record for the given type array - static void dump_prim_array(DumpWriter* writer, typeArrayOop array); + static void dump_prim_array(DumpWriter* writer, typeArrayOop array, bool minidump = false); // create HPROF_FRAME record for the given method and bci static void dump_stack_frame(DumpWriter* writer, int frame_serial_num, int class_serial_num, Method* m, int bci); }; @@ -1070,17 +1070,21 @@ void DumperSupport::dump_object_array(DumpWriter* writer, objArrayOop array) { // creates HPROF_GC_PRIM_ARRAY_DUMP record for the given type array -void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) { +void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array, bool minidump) { BasicType type = TypeArrayKlass::cast(array->klass())->element_type(); writer->write_u1(HPROF_GC_PRIM_ARRAY_DUMP); writer->write_objectID(array); writer->write_u4(STACK_TRACE_ID); - writer->write_u4((u4)array->length()); + if (!minidump) { + writer->write_u4((u4)array->length()); + } else { + writer->write_u4(0); + } writer->write_u1(type2tag(type)); // nothing to copy - if (array->length() == 0) { + if (array->length() == 0 || minidump) { return; } @@ -1317,6 +1321,8 @@ class HeapObjectDumper : public ObjectClosure { // used to indicate that a record has been writen void mark_end_of_record(); + bool using_minidump(); + public: HeapObjectDumper(VM_HeapDumper* dumper, DumpWriter* writer) { _dumper = dumper; @@ -1348,7 +1354,11 @@ void HeapObjectDumper::do_object(oop o) { mark_end_of_record(); } else if (o->is_typeArray()) { // create a HPROF_GC_PRIM_ARRAY_DUMP record for each type array - DumperSupport::dump_prim_array(writer(), typeArrayOop(o)); + if (using_minidump()) { + DumperSupport::dump_prim_array(writer(), typeArrayOop(o), true); + } else { + DumperSupport::dump_prim_array(writer(), typeArrayOop(o)); + } mark_end_of_record(); } } @@ -1368,6 +1378,8 @@ class VM_HeapDumper : public VM_GC_Operation { ThreadStackTrace** _stack_traces; int _num_threads; + HeapDumper* _heap_dumper; + // accessors and setters static VM_HeapDumper* dumper() { assert(_global_dumper != NULL, "Error"); return _global_dumper; } static DumpWriter* writer() { assert(_global_writer != NULL, "Error"); return _global_writer; } @@ -1422,7 +1434,7 @@ class VM_HeapDumper : public VM_GC_Operation { void end_of_dump(); public: - VM_HeapDumper(DumpWriter* writer, bool gc_before_heap_dump, bool oome) : + VM_HeapDumper(DumpWriter* writer, bool gc_before_heap_dump, bool oome, HeapDumper* heap_dumper = NULL) : VM_GC_Operation(0 /* total collections, dummy, ignored */, GCCause::_heap_dump /* GC Cause */, 0 /* total full collections, dummy, ignored */, @@ -1434,6 +1446,7 @@ class VM_HeapDumper : public VM_GC_Operation { _klass_map = new (ResourceObj::C_HEAP, mtInternal) GrowableArray(INITIAL_CLASS_COUNT, true); _stack_traces = NULL; _num_threads = 0; + _heap_dumper = heap_dumper; if (oome) { assert(!Thread::current()->is_VM_thread(), "Dump from OutOfMemoryError cannot be called by the VMThread"); // get OutOfMemoryError zero-parameter constructor @@ -1461,11 +1474,17 @@ class VM_HeapDumper : public VM_GC_Operation { // used to mark sub-record boundary void check_segment_length(); void doit(); + + HeapDumper* heap_dumper() { return _heap_dumper; } }; VM_HeapDumper* VM_HeapDumper::_global_dumper = NULL; DumpWriter* VM_HeapDumper::_global_writer = NULL; +bool HeapObjectDumper::using_minidump() { + return _dumper->heap_dumper()->is_mini_dump(); +} + bool VM_HeapDumper::skip_operation() const { return false; } @@ -1903,7 +1922,7 @@ int HeapDumper::dump(const char* path) { } // generate the dump - VM_HeapDumper dumper(&writer, _gc_before_heap_dump, _oome); + VM_HeapDumper dumper(&writer, _gc_before_heap_dump, _oome, this); if (Thread::current()->is_VM_thread()) { assert(SafepointSynchronize::is_at_safepoint(), "Expected to be called at a safepoint"); dumper.doit(); diff --git a/src/share/vm/services/heapDumper.hpp b/src/share/vm/services/heapDumper.hpp index 0bf6ba7be..89afddf21 100644 --- a/src/share/vm/services/heapDumper.hpp +++ b/src/share/vm/services/heapDumper.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2019, 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 @@ -47,6 +47,7 @@ class HeapDumper : public StackObj { bool _print_to_tty; bool _gc_before_heap_dump; bool _oome; + bool _mini_dump; elapsedTimer _t; HeapDumper(bool gc_before_heap_dump, bool print_to_tty, bool oome) : @@ -65,8 +66,12 @@ class HeapDumper : public StackObj { static void dump_heap(bool oome); public: - HeapDumper(bool gc_before_heap_dump) : - _gc_before_heap_dump(gc_before_heap_dump), _error(NULL), _print_to_tty(false), _oome(false) { } + HeapDumper(bool gc_before_heap_dump, bool mini_dump = false) : + _gc_before_heap_dump(gc_before_heap_dump), + _error(NULL), + _print_to_tty(false), + _oome(false), + _mini_dump(mini_dump) { } ~HeapDumper(); @@ -79,6 +84,8 @@ class HeapDumper : public StackObj { static void dump_heap() NOT_SERVICES_RETURN; static void dump_heap_from_oome() NOT_SERVICES_RETURN; + + inline bool is_mini_dump() const { return _mini_dump; } }; #endif // SHARE_VM_SERVICES_HEAPDUMPER_HPP diff --git a/test/serviceability/sa/jmap-minidump/TestMiniDump.sh b/test/serviceability/sa/jmap-minidump/TestMiniDump.sh new file mode 100644 index 000000000..8f5a70d64 --- /dev/null +++ b/test/serviceability/sa/jmap-minidump/TestMiniDump.sh @@ -0,0 +1,127 @@ +# +# Copyright (c) 2019 Alibaba Group Holding Limited. 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. Alibaba designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# 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. +# + +# +# @test +# @summary Test 'jmap -dump:mini' +# @run shell/timeout=500 TestMiniDump.sh +# + +if [ "${TESTSRC}" = "" ] +then + TESTSRC=${PWD} + echo "TESTSRC not set. Using "${TESTSRC}" as default" +fi +echo "TESTSRC=${TESTSRC}" +## Adding common setup Variables for running shell tests. +. ${TESTSRC}/../../../test_env.sh + +JAVA=${TESTJAVA}${FS}bin${FS}java +JAVAC=${TESTJAVA}${FS}bin${FS}javac +JMAP=${TESTJAVA}${FS}bin${FS}jmap +JCMD=${TESTJAVA}${FS}bin${FS}jcmd +JPS=${TESTJAVA}${FS}bin${FS}jps +# A simple testcase used to invoke JVM +TEST_CLASS=Loop_$(date +%Y%m%d%H%M%S) +TEST_SOURCE=$TEST_CLASS.java + +cat > $TEST_SOURCE << EOF +public class ${TEST_CLASS} { + public static void main(String[] args) { + // allocate 1G temp objects + for (int i = 0; i < 1024 * 1024; ++i) { + Object o = new byte[1024]; + } + // keep Java process running + while (true); + } +} +EOF + +# compile the test class +$JAVAC $TEST_SOURCE +if [ $? != '0' ]; then + echo "Failed to compile Foo.java" + exit 1 +fi + +${JAVA} -cp . -Xmx4g -Xms4g -Xmn2g ${TEST_CLASS}& + +# wait child java process to allocate memory +sleep 5 + +PID=$(${JPS} | grep ${TEST_CLASS} | awk '{print $1}') +if [ $? != 0 ] || [ -z "${PID}" ]; then exit 1; fi + +# full dump must be > 1G +FULL_DUMP_SIZE_THRESHOLD=$(( 1024 * 1024 * 1024)) +# mini dump must be < 30MB +MINI_DUMP_SIZE_THRESHOLD=$(( 30 * 1024 * 1024)) + +# Test of full heap dump +DUMP="full_heap.bin" +${JMAP} -dump:format=b,file=${DUMP} ${PID} +if [ $? != 0 ] || [ ! -f "${PWD}/${DUMP}" ]; then exit 1; fi + +SIZE=$(ls -l | grep ${DUMP} | awk '{print $5}') +if [ $? != 0 ] || [ ${SIZE} -le "${FULL_DUMP_SIZE_THRESHOLD}" ]; then + echo "Full heap dump is too small" + exit 1 +fi + +# full heap dump from jcmd +DUMP="full_heap2.bin" +${JCMD} ${PID} GC.heap_dump -all ${DUMP} +if [ $? != 0 ] || [ ! -f "${PWD}/${DUMP}" ]; then exit 1; fi + +SIZE=$(ls -l | grep ${DUMP} | awk '{print $5}') +if [ $? != 0 ] || [ ${SIZE} -lt "${FULL_DUMP_SIZE_THRESHOLDL}" ]; then + echo "Full heap dump is too small" + exit 1 +fi + +# Test of mini heap dump +DUMP="mini_heap.bin" +${JMAP} -dump:format=b,mini,file=${DUMP} ${PID} +if [ $? != 0 ] || [ ! -f "${PWD}/${DUMP}" ]; then exit 1; fi +SIZE=$(ls -l | grep ${DUMP} | awk '{print $5}') +if [ $? != 0 ] || [ ${SIZE} -ge "${MINI_DUMP_SIZE_THRESHOLD}" ]; then + echo "Mini heap dump is too large" + exit 1 +fi + +# minidump from jcmd +DUMP="mini_heap2.bin" +${JCMD} ${PID} GC.heap_dump -all -mini ${DUMP} +if [ $? != 0 ] || [ ! -f "${PWD}/${DUMP}" ]; then exit 1; fi +SIZE=$(ls -l | grep ${DUMP} | awk '{print $5}') +if [ $? != 0 ] || [ ${SIZE} -ge "${MINI_DUMP_SIZE_THRESHOLD}" ]; then + echo "Mini heap dump is too large" + exit 1 +fi + +# clean up +rm -f *.bin +if [ $? != 0 ]; then exit 1; fi +kill -9 ${PID} +if [ $? != 0 ]; then exit 1; fi + +exit 0