提交 a4d467b2 编写于 作者: A andrew

Merge

...@@ -1294,3 +1294,5 @@ ee19c358e3b8deeda2f64d660a0870df7b1abd49 jdk8u242-b03 ...@@ -1294,3 +1294,5 @@ ee19c358e3b8deeda2f64d660a0870df7b1abd49 jdk8u242-b03
8b80409d5840142a27e274d33948f483a6406a50 jdk8u242-b07 8b80409d5840142a27e274d33948f483a6406a50 jdk8u242-b07
7c9f6b5f8d119dc1ba3c5536595ce3ae7414599d jdk8u242-b08 7c9f6b5f8d119dc1ba3c5536595ce3ae7414599d jdk8u242-b08
7c9f6b5f8d119dc1ba3c5536595ce3ae7414599d jdk8u242-ga 7c9f6b5f8d119dc1ba3c5536595ce3ae7414599d jdk8u242-ga
8c0733543544bbcd32c4404630d764d280299056 jdk8u252-b00
a67e9c6edcdd73cb860a16990f0905e102c282d7 jdk8u252-b01
...@@ -868,6 +868,9 @@ err: ...@@ -868,6 +868,9 @@ err:
#define LINK_MAP_LD_OFFSET offsetof(struct link_map, l_ld) #define LINK_MAP_LD_OFFSET offsetof(struct link_map, l_ld)
#define LINK_MAP_NEXT_OFFSET offsetof(struct link_map, l_next) #define LINK_MAP_NEXT_OFFSET offsetof(struct link_map, l_next)
#define INVALID_LOAD_ADDRESS -1L
#define ZERO_LOAD_ADDRESS 0x0L
// Calculate the load address of shared library // Calculate the load address of shared library
// on prelink-enabled environment. // on prelink-enabled environment.
// //
...@@ -884,7 +887,7 @@ static uintptr_t calc_prelinked_load_address(struct ps_prochandle* ph, int lib_f ...@@ -884,7 +887,7 @@ static uintptr_t calc_prelinked_load_address(struct ps_prochandle* ph, int lib_f
phbuf = read_program_header_table(lib_fd, elf_ehdr); phbuf = read_program_header_table(lib_fd, elf_ehdr);
if (phbuf == NULL) { if (phbuf == NULL) {
print_debug("can't read program header of shared object\n"); print_debug("can't read program header of shared object\n");
return 0L; return INVALID_LOAD_ADDRESS;
} }
// Get the address of .dynamic section from shared library. // Get the address of .dynamic section from shared library.
...@@ -900,7 +903,7 @@ static uintptr_t calc_prelinked_load_address(struct ps_prochandle* ph, int lib_f ...@@ -900,7 +903,7 @@ static uintptr_t calc_prelinked_load_address(struct ps_prochandle* ph, int lib_f
if (ps_pdread(ph, (psaddr_t)link_map_addr + LINK_MAP_LD_OFFSET, if (ps_pdread(ph, (psaddr_t)link_map_addr + LINK_MAP_LD_OFFSET,
&lib_ld, sizeof(uintptr_t)) != PS_OK) { &lib_ld, sizeof(uintptr_t)) != PS_OK) {
print_debug("can't read address of dynamic section in shared object\n"); print_debug("can't read address of dynamic section in shared object\n");
return 0L; return INVALID_LOAD_ADDRESS;
} }
// Return the load address which is calculated by the address of .dynamic // Return the load address which is calculated by the address of .dynamic
...@@ -1011,9 +1014,9 @@ static bool read_shared_lib_info(struct ps_prochandle* ph) { ...@@ -1011,9 +1014,9 @@ static bool read_shared_lib_info(struct ps_prochandle* ph) {
// continue with other libraries... // continue with other libraries...
} else { } else {
if (read_elf_header(lib_fd, &elf_ehdr)) { if (read_elf_header(lib_fd, &elf_ehdr)) {
if (lib_base_diff == 0x0L) { if (lib_base_diff == ZERO_LOAD_ADDRESS) {
lib_base_diff = calc_prelinked_load_address(ph, lib_fd, &elf_ehdr, link_map_addr); lib_base_diff = calc_prelinked_load_address(ph, lib_fd, &elf_ehdr, link_map_addr);
if (lib_base_diff == 0x0L) { if (lib_base_diff == INVALID_LOAD_ADDRESS) {
close(lib_fd); close(lib_fd);
return false; return false;
} }
......
...@@ -119,6 +119,7 @@ CFLAGS += $(CFLAGS/NOEX) ...@@ -119,6 +119,7 @@ CFLAGS += $(CFLAGS/NOEX)
# Extra flags from gnumake's invocation or environment # Extra flags from gnumake's invocation or environment
CFLAGS += $(EXTRA_CFLAGS) CFLAGS += $(EXTRA_CFLAGS)
LFLAGS += $(EXTRA_CFLAGS) $(EXTRA_LDFLAGS) LFLAGS += $(EXTRA_CFLAGS) $(EXTRA_LDFLAGS)
ASFLAGS += $(EXTRA_ASFLAGS)
# Don't set excutable bit on stack segment # Don't set excutable bit on stack segment
# the same could be done by separate execstack command # the same could be done by separate execstack command
......
...@@ -121,6 +121,7 @@ CFLAGS += $(CFLAGS/NOEX) ...@@ -121,6 +121,7 @@ CFLAGS += $(CFLAGS/NOEX)
# Extra flags from gnumake's invocation or environment # Extra flags from gnumake's invocation or environment
CFLAGS += $(EXTRA_CFLAGS) CFLAGS += $(EXTRA_CFLAGS)
LFLAGS += $(EXTRA_CFLAGS) $(EXTRA_LDFLAGS) LFLAGS += $(EXTRA_CFLAGS) $(EXTRA_LDFLAGS)
ASFLAGS += $(EXTRA_ASFLAGS)
# Don't set excutable bit on stack segment # Don't set excutable bit on stack segment
# the same could be done by separate execstack command # the same could be done by separate execstack command
......
...@@ -124,6 +124,7 @@ CFLAGS += $(CFLAGS/NOEX) ...@@ -124,6 +124,7 @@ CFLAGS += $(CFLAGS/NOEX)
# Extra flags from gnumake's invocation or environment # Extra flags from gnumake's invocation or environment
CFLAGS += $(EXTRA_CFLAGS) CFLAGS += $(EXTRA_CFLAGS)
LFLAGS += $(EXTRA_CFLAGS) $(EXTRA_LDFLAGS) LFLAGS += $(EXTRA_CFLAGS) $(EXTRA_LDFLAGS)
ASFLAGS += $(EXTRA_ASFLAGS)
# Don't set excutable bit on stack segment # Don't set excutable bit on stack segment
# the same could be done by separate execstack command # the same could be done by separate execstack command
......
...@@ -109,6 +109,7 @@ CFLAGS += $(CFLAGS/NOEX) ...@@ -109,6 +109,7 @@ CFLAGS += $(CFLAGS/NOEX)
# Extra flags from gnumake's invocation or environment # Extra flags from gnumake's invocation or environment
CFLAGS += $(EXTRA_CFLAGS) CFLAGS += $(EXTRA_CFLAGS)
ASFLAGS += $(EXTRA_ASFLAGS)
# Math Library (libm.so), do not use -lm. # Math Library (libm.so), do not use -lm.
# There might be two versions of libm.so on the build system: # There might be two versions of libm.so on the build system:
......
/* /*
* Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -89,200 +89,6 @@ char *FileBuff::get_line(void) { ...@@ -89,200 +89,6 @@ char *FileBuff::get_line(void) {
return retval; return retval;
} }
//------------------------------FileBuffRegion---------------------------------
// Create a new region in a FileBuff.
FileBuffRegion::FileBuffRegion( FileBuff* bufr, int soln, int ln,
int off, int len)
: _bfr(bufr), _sol(soln), _line(ln), _offset(off), _length(len) {
_next = NULL; // No chained regions
}
//------------------------------~FileBuffRegion--------------------------------
// Delete the entire linked list of buffer regions.
FileBuffRegion::~FileBuffRegion() {
if( _next ) delete _next;
}
//------------------------------copy-------------------------------------------
// Deep copy a FileBuffRegion
FileBuffRegion *FileBuffRegion::copy() {
if( !this ) return NULL; // The empty buffer region
FileBuffRegion *br = new FileBuffRegion(_bfr,_sol,_line,_offset,_length);
if( _next ) br->_next = _next->copy();
return br;
}
//------------------------------merge------------------------------------------
// Merge another buffer region into this buffer region. Make overlapping areas
// become a single region. Remove (delete) the input FileBuffRegion.
// Since the buffer regions are sorted by file offset, this is a varient of a
// "sorted-merge" running in linear time.
FileBuffRegion *FileBuffRegion::merge( FileBuffRegion *br ) {
if( !br ) return this; // Merging nothing
if( !this ) return br; // Merging into nothing
assert( _bfr == br->_bfr, "" ); // Check for pointer-equivalent buffers
if( _offset < br->_offset ) { // "this" starts before "br"
if( _offset+_length < br->_offset ) { // "this" ends before "br"
if( _next ) _next->merge( br ); // Merge with remainder of list
else _next = br; // No more in this list; just append.
} else { // Regions overlap.
int l = br->_offset + br->_length - _offset;
if( l > _length ) _length = l; // Pick larger region
FileBuffRegion *nr = br->_next; // Get rest of region
br->_next = NULL; // Remove indication of rest of region
delete br; // Delete this region (it's been subsumed).
if( nr ) merge( nr ); // Merge with rest of region
} // End of if regions overlap or not.
} else { // "this" starts after "br"
if( br->_offset+br->_length < _offset ) { // "br" ends before "this"
FileBuffRegion *nr = new FileBuffRegion(_bfr,_sol,_line,_offset,_length);
nr->_next = _next; // Structure copy "this" guy to "nr"
*this = *br; // Structure copy "br" over "this".
br->_next = NULL; // Remove indication of rest of region
delete br; // Delete this region (it's been copied)
merge( nr ); // Finish merging
} else { // Regions overlap.
int l = _offset + _length - br->_offset;
if( l > _length ) _length = l; // Pick larger region
_offset = br->_offset; // Start with earlier region
_sol = br->_sol; // Also use earlier line start
_line = br->_line; // Also use earlier line
FileBuffRegion *nr = br->_next; // Get rest of region
br->_next = NULL; // Remove indication of rest of region
delete br; // Delete this region (it's been subsumed).
if( nr ) merge( nr ); // Merge with rest of region
} // End of if regions overlap or not.
}
return this;
}
//------------------------------expandtab--------------------------------------
static int expandtab( ostream &os, int off, char c, char fill1, char fill2 ) {
if( c == '\t' ) { // Tab?
do os << fill1; // Expand the tab; Output space
while( (++off) & 7 ); // Expand to tab stop
} else { // Normal character
os << fill2; // Display normal character
off++; // Increment "cursor" offset
}
return off;
}
//------------------------------printline--------------------------------------
// Print and highlite a region of a line. Return the amount of highliting left
// to do (i.e. highlite length minus length of line).
static int printline( ostream& os, const char *fname, int line,
const char *_sol, int skip, int len ) {
// Display the entire tab-expanded line
os << fname << ":" << line << ": ";
const char *t = strchr(_sol,'\n')+1; // End of line
int off = 0; // Cursor offset for tab expansion
const char *s = _sol; // Nice string pointer
while( t-s ) { // Display whole line
char c = *s++; // Get next character to display
off = expandtab(os,off,c,' ',c);
}
// Display the tab-expanded skippings before underlining.
os << fname << ":" << line << ": ";
off = 0; // Cursor offset for tab expansion
s = _sol; // Restart string pointer
// Start underlining.
if( skip != -1 ) { // The no-start-indicating flag
const char *u = _sol+skip; // Amount to skip
while( u-s ) // Display skipped part
off = expandtab(os,off,*s++,' ',' ');
os << '^'; // Start region
off++; // Moved cursor
len--; // 1 less char to do
if( *s++ == '\t' ) // Starting character is a tab?
off = expandtab(os,off,'\t','-','^');
}
// Long region doesn't end on this line
int llen = (int)(t-s); // Length of line, minus what's already done
if( len > llen ) { // Doing entire rest of line?
while( t-s ) // Display rest of line
off = expandtab(os,off,*s++,'-','-');
os << '\n'; // EOL
return len-llen; // Return what's not yet done.
}
// Region does end on this line. This code fails subtly if the region ends
// in a tab character.
int i;
for( i=1; i<len; i++ ) // Underline just what's needed
off = expandtab(os,off,*s++,'-','-');
if( i == len ) os << '^'; // Mark end of region
os << '\n'; // End of marked line
return 0; // All done
}
//------------------------------print------------------------------------------
//std::ostream& operator<< ( std::ostream& os, FileBuffRegion &br ) {
ostream& operator<< ( ostream& os, FileBuffRegion &br ) {
if( &br == NULL ) return os; // The empty buffer region
FileBuffRegion *brp = &br; // Pointer to region
while( brp ) { // While have chained regions
brp->print(os); // Print region
brp = brp->_next; // Chain to next
}
return os; // Return final stream
}
//------------------------------print------------------------------------------
// Print the FileBuffRegion to a stream. FileBuffRegions are printed with the
// filename and line number to the left, and complete text lines to the right.
// Selected portions (portions of a line actually in the FileBuffRegion are
// underlined. Ellipses are used for long multi-line regions.
//void FileBuffRegion::print( std::ostream& os ) {
void FileBuffRegion::print( ostream& os ) {
if( !this ) return; // Nothing to print
char *s = _bfr->get_line();
int skip = (int)(_offset - _sol); // Amount to skip to start of data
int len = printline( os, _bfr->_fp->_name, _line, s, skip, _length );
if( !len ) return; // All done; exit
// Here we require at least 2 lines
int off1 = _length - len + skip; // Length of line 1
int off2 = off1 + _sol; // Offset to start of line 2
char *s2 = _bfr->get_line(); // Start of line 2
char *s3 = strchr( s2, '\n' )+1; // Start of line 3 (unread)
if( len <= (s3-s2) ) { // It all fits on the next line
printline( os, _bfr->_fp->_name, _line+1, s2, -1, len ); // Print&underline
return;
}
// Here we require at least 3 lines
int off3 = off2 + (int)(s3-s2); // Offset to start of line 3
s3 = _bfr->get_line(); // Start of line 3 (read)
const char *s4 = strchr( s3, '\n' )+1;// Start of line 4 (unread)
if( len < (s4-s3) ) { // It all fits on the next 2 lines
s2 = _bfr->get_line();
len = printline( os, _bfr->_fp->_name, _line+1, s2, -1, len ); // Line 2
s3 = _bfr->get_line();
printline( os, _bfr->_fp->_name, _line+2, s3, -1, len ); // Line 3
return;
}
// Here we require at least 4 lines.
// Print only the 1st and last line, with ellipses in middle.
os << "...\n"; // The ellipses
int cline = _line+1; // Skipped 2 lines
do { // Do until find last line
len -= (int)(s3-s2); // Remove length of line
cline++; // Next line
s2 = _bfr->get_line(); // Get next line from end of this line
s3 = strchr( s2, '\n' ) + 1;// Get end of next line
} while( len > (s3-s2) ); // Repeat until last line
printline( os, _bfr->_fp->_name, cline, s2, -1, len ); // Print & underline
}
//------------------------------file_error------------------------------------- //------------------------------file_error-------------------------------------
void FileBuff::file_error(int flag, int linenum, const char *fmt, ...) void FileBuff::file_error(int flag, int linenum, const char *fmt, ...)
{ {
......
/* /*
* Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -46,7 +46,6 @@ class ArchDesc; ...@@ -46,7 +46,6 @@ class ArchDesc;
// This class defines a nicely behaved buffer of text. Entire file of text // This class defines a nicely behaved buffer of text. Entire file of text
// is read into buffer at creation, with sentinels at start and end. // is read into buffer at creation, with sentinels at start and end.
class FileBuff { class FileBuff {
friend class FileBuffRegion;
private: private:
long _bufferSize; // Size of text holding buffer. long _bufferSize; // Size of text holding buffer.
long _offset; // Expected filepointer offset. long _offset; // Expected filepointer offset.
...@@ -82,29 +81,4 @@ class FileBuff { ...@@ -82,29 +81,4 @@ class FileBuff {
// when the pointer is valid (i.e. just obtained from getline()). // when the pointer is valid (i.e. just obtained from getline()).
long getoff(const char* s) { return _bufoff + (long)(s - _buf); } long getoff(const char* s) { return _bufoff + (long)(s - _buf); }
}; };
//------------------------------FileBuffRegion---------------------------------
// A buffer region is really a region of some file, specified as a linked list
// of offsets and lengths. These regions can be merged; overlapping regions
// will coalesce.
class FileBuffRegion {
public: // Workaround dev-studio friend/private bug
FileBuffRegion *_next; // Linked list of regions sorted by offset.
private:
FileBuff *_bfr; // The Buffer of the file
int _offset, _length; // The file area
int _sol; // Start of line where the file area starts
int _line; // First line of region
public:
FileBuffRegion(FileBuff*, int sol, int line, int offset, int len);
~FileBuffRegion();
FileBuffRegion *copy(); // Deep copy
FileBuffRegion *merge(FileBuffRegion*); // Merge 2 regions; delete input
void print(ostream&);
friend ostream& operator<< (ostream&, FileBuffRegion&);
};
#endif // SHARE_VM_ADLC_FILEBUFF_HPP #endif // SHARE_VM_ADLC_FILEBUFF_HPP
...@@ -360,9 +360,6 @@ ...@@ -360,9 +360,6 @@
"File to dump ideal graph to. If set overrides the " \ "File to dump ideal graph to. If set overrides the " \
"use of the network") \ "use of the network") \
\ \
product(bool, UseOldInlining, true, \
"Enable the 1.3 inlining strategy") \
\
product(bool, UseBimorphicInlining, true, \ product(bool, UseBimorphicInlining, true, \
"Profiling based inlining for two receivers") \ "Profiling based inlining for two receivers") \
\ \
......
...@@ -2485,7 +2485,8 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { ...@@ -2485,7 +2485,8 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) {
assert(mem != mem->in(MemNode::Memory), "dead loop in StoreNode::Ideal"); assert(mem != mem->in(MemNode::Memory), "dead loop in StoreNode::Ideal");
assert(Opcode() == mem->Opcode() || assert(Opcode() == mem->Opcode() ||
phase->C->get_alias_index(adr_type()) == Compile::AliasIdxRaw, phase->C->get_alias_index(adr_type()) == Compile::AliasIdxRaw ||
(is_mismatched_access() || mem->as_Store()->is_mismatched_access()),
"no mismatched stores, except on raw memory"); "no mismatched stores, except on raw memory");
if (mem->outcnt() == 1 && // check for intervening uses if (mem->outcnt() == 1 && // check for intervening uses
......
...@@ -302,9 +302,10 @@ static ObsoleteFlag obsolete_jvm_flags[] = { ...@@ -302,9 +302,10 @@ static ObsoleteFlag obsolete_jvm_flags[] = {
{ "UsePermISM", JDK_Version::jdk(8), JDK_Version::jdk(9) }, { "UsePermISM", JDK_Version::jdk(8), JDK_Version::jdk(9) },
{ "UseMPSS", JDK_Version::jdk(8), JDK_Version::jdk(9) }, { "UseMPSS", JDK_Version::jdk(8), JDK_Version::jdk(9) },
{ "UseStringCache", JDK_Version::jdk(8), JDK_Version::jdk(9) }, { "UseStringCache", JDK_Version::jdk(8), JDK_Version::jdk(9) },
{ "UseOldInlining", JDK_Version::jdk(9), JDK_Version::jdk(10) }, { "UseOldInlining", JDK_Version::jdk_update(8, 20), JDK_Version::jdk(10) },
{ "AutoShutdownNMT", JDK_Version::jdk(9), JDK_Version::jdk(10) }, { "AutoShutdownNMT", JDK_Version::jdk_update(8, 40), JDK_Version::jdk(10) },
{ "CompilationRepeat", JDK_Version::jdk(8), JDK_Version::jdk(9) }, { "CompilationRepeat", JDK_Version::jdk(8), JDK_Version::jdk(9) },
{ "SegmentedHeapDumpThreshold", JDK_Version::jdk_update(8, 252), JDK_Version::jdk(10) },
#ifdef PRODUCT #ifdef PRODUCT
{ "DesiredMethodLimit", { "DesiredMethodLimit",
JDK_Version::jdk_update(7, 2), JDK_Version::jdk(8) }, JDK_Version::jdk_update(7, 2), JDK_Version::jdk(8) },
......
...@@ -507,6 +507,7 @@ void VMThread::loop() { ...@@ -507,6 +507,7 @@ void VMThread::loop() {
_cur_vm_operation = safepoint_ops; _cur_vm_operation = safepoint_ops;
if (_cur_vm_operation != NULL) { if (_cur_vm_operation != NULL) {
do { do {
EventMark em("Executing coalesced safepoint VM operation: %s", _cur_vm_operation->name());
// evaluate_operation deletes the op object so we have // evaluate_operation deletes the op object so we have
// to grab the next op now // to grab the next op now
VM_Operation* next = _cur_vm_operation->next(); VM_Operation* next = _cur_vm_operation->next();
......
...@@ -50,8 +50,7 @@ ...@@ -50,8 +50,7 @@
* src/share/demo/jvmti/hprof/hprof_io.c * src/share/demo/jvmti/hprof/hprof_io.c
* *
* *
* header "JAVA PROFILE 1.0.1" or "JAVA PROFILE 1.0.2" * header "JAVA PROFILE 1.0.2" (0-terminated)
* (0-terminated)
* *
* u4 size of identifiers. Identifiers are used to represent * u4 size of identifiers. Identifiers are used to represent
* UTF8 strings, objects, stack traces, etc. They usually * UTF8 strings, objects, stack traces, etc. They usually
...@@ -382,6 +381,8 @@ class DumpWriter : public StackObj { ...@@ -382,6 +381,8 @@ class DumpWriter : public StackObj {
size_t _size; size_t _size;
size_t _pos; size_t _pos;
jlong _dump_start;
char* _error; // error message when I/O fails char* _error; // error message when I/O fails
void set_file_descriptor(int fd) { _fd = fd; } void set_file_descriptor(int fd) { _fd = fd; }
...@@ -405,6 +406,10 @@ class DumpWriter : public StackObj { ...@@ -405,6 +406,10 @@ class DumpWriter : public StackObj {
bool is_open() const { return file_descriptor() >= 0; } bool is_open() const { return file_descriptor() >= 0; }
void flush(); void flush();
jlong dump_start() const { return _dump_start; }
void set_dump_start(jlong pos);
julong current_record_length();
// total number of bytes written to the disk // total number of bytes written to the disk
julong bytes_written() const { return _bytes_written; } julong bytes_written() const { return _bytes_written; }
...@@ -446,6 +451,7 @@ DumpWriter::DumpWriter(const char* path) { ...@@ -446,6 +451,7 @@ DumpWriter::DumpWriter(const char* path) {
_pos = 0; _pos = 0;
_error = NULL; _error = NULL;
_bytes_written = 0L; _bytes_written = 0L;
_dump_start = (jlong)-1;
_fd = os::create_binary_file(path, false); // don't replace existing file _fd = os::create_binary_file(path, false); // don't replace existing file
// if the open failed we record the error // if the open failed we record the error
...@@ -473,6 +479,22 @@ void DumpWriter::close() { ...@@ -473,6 +479,22 @@ void DumpWriter::close() {
} }
} }
// sets the dump starting position
void DumpWriter::set_dump_start(jlong pos) {
_dump_start = pos;
}
julong DumpWriter::current_record_length() {
if (is_open()) {
// calculate the size of the dump record
julong dump_end = bytes_written() + bytes_unwritten();
assert(dump_end == (size_t)current_offset(), "checking");
julong dump_len = dump_end - dump_start() - 4;
return dump_len;
}
return 0;
}
// write directly to the file // write directly to the file
void DumpWriter::write_internal(void* s, size_t len) { void DumpWriter::write_internal(void* s, size_t len) {
if (is_open()) { if (is_open()) {
...@@ -641,6 +663,18 @@ class DumperSupport : AllStatic { ...@@ -641,6 +663,18 @@ class DumperSupport : AllStatic {
static void dump_prim_array(DumpWriter* writer, typeArrayOop array); static void dump_prim_array(DumpWriter* writer, typeArrayOop array);
// create HPROF_FRAME record for the given method and bci // 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); static void dump_stack_frame(DumpWriter* writer, int frame_serial_num, int class_serial_num, Method* m, int bci);
// check if we need to truncate an array
static int calculate_array_max_length(DumpWriter* writer, arrayOop array, short header_size);
// writes a HPROF_HEAP_DUMP_SEGMENT record
static void write_dump_header(DumpWriter* writer);
// fixes up the length of the current dump record
static void write_current_dump_record_length(DumpWriter* writer);
// fixes up the current dump record and writes HPROF_HEAP_DUMP_END record
static void end_of_dump(DumpWriter* writer);
}; };
// write a header of the given type // write a header of the given type
...@@ -1047,50 +1081,103 @@ void DumperSupport::dump_basic_type_array_class(DumpWriter* writer, Klass* k) { ...@@ -1047,50 +1081,103 @@ void DumperSupport::dump_basic_type_array_class(DumpWriter* writer, Klass* k) {
} }
} }
// Hprof uses an u4 as record length field,
// which means we need to truncate arrays that are too long.
int DumperSupport::calculate_array_max_length(DumpWriter* writer, arrayOop array, short header_size) {
BasicType type = ArrayKlass::cast(array->klass())->element_type();
assert(type >= T_BOOLEAN && type <= T_OBJECT, "invalid array element type");
int length = array->length();
int type_size;
if (type == T_OBJECT) {
type_size = sizeof(address);
} else {
type_size = type2aelembytes(type);
}
size_t length_in_bytes = (size_t)length * type_size;
// Create a new record if the current record is non-empty and the array can't fit.
julong current_record_length = writer->current_record_length();
if (current_record_length > 0 &&
(current_record_length + header_size + length_in_bytes) > max_juint) {
write_current_dump_record_length(writer);
write_dump_header(writer);
// We now have an empty record.
current_record_length = 0;
}
// Calculate max bytes we can use.
uint max_bytes = max_juint - (header_size + current_record_length);
// Array too long for the record?
// Calculate max length and return it.
if (length_in_bytes > max_bytes) {
length = max_bytes / type_size;
length_in_bytes = (size_t)length * type_size;
warning("cannot dump array of type %s[] with length %d; truncating to length %d",
type2name_tab[type], array->length(), length);
}
return length;
}
// creates HPROF_GC_OBJ_ARRAY_DUMP record for the given object array // creates HPROF_GC_OBJ_ARRAY_DUMP record for the given object array
void DumperSupport::dump_object_array(DumpWriter* writer, objArrayOop array) { void DumperSupport::dump_object_array(DumpWriter* writer, objArrayOop array) {
// sizeof(u1) + 2 * sizeof(u4) + sizeof(objectID) + sizeof(classID)
short header_size = 1 + 2 * 4 + 2 * sizeof(address);
int length = calculate_array_max_length(writer, array, header_size);
writer->write_u1(HPROF_GC_OBJ_ARRAY_DUMP); writer->write_u1(HPROF_GC_OBJ_ARRAY_DUMP);
writer->write_objectID(array); writer->write_objectID(array);
writer->write_u4(STACK_TRACE_ID); writer->write_u4(STACK_TRACE_ID);
writer->write_u4((u4)array->length()); writer->write_u4(length);
// array class ID // array class ID
writer->write_classID(array->klass()); writer->write_classID(array->klass());
// [id]* elements // [id]* elements
for (int index=0; index<array->length(); index++) { for (int index = 0; index < length; index++) {
oop o = array->obj_at(index); oop o = array->obj_at(index);
writer->write_objectID(o); writer->write_objectID(o);
} }
} }
#define WRITE_ARRAY(Array, Type, Size) \ #define WRITE_ARRAY(Array, Type, Size, Length) \
for (int i=0; i<Array->length(); i++) { writer->write_##Size((Size)array->Type##_at(i)); } for (int i = 0; i < Length; i++) { writer->write_##Size((Size)array->Type##_at(i)); }
// creates HPROF_GC_PRIM_ARRAY_DUMP record for the given type 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) {
BasicType type = TypeArrayKlass::cast(array->klass())->element_type(); BasicType type = TypeArrayKlass::cast(array->klass())->element_type();
// 2 * sizeof(u1) + 2 * sizeof(u4) + sizeof(objectID)
short header_size = 2 * 1 + 2 * 4 + sizeof(address);
int length = calculate_array_max_length(writer, array, header_size);
int type_size = type2aelembytes(type);
u4 length_in_bytes = (u4)length * type_size;
writer->write_u1(HPROF_GC_PRIM_ARRAY_DUMP); writer->write_u1(HPROF_GC_PRIM_ARRAY_DUMP);
writer->write_objectID(array); writer->write_objectID(array);
writer->write_u4(STACK_TRACE_ID); writer->write_u4(STACK_TRACE_ID);
writer->write_u4((u4)array->length()); writer->write_u4(length);
writer->write_u1(type2tag(type)); writer->write_u1(type2tag(type));
// nothing to copy // nothing to copy
if (array->length() == 0) { if (length == 0) {
return; return;
} }
// If the byte ordering is big endian then we can copy most types directly // If the byte ordering is big endian then we can copy most types directly
u4 length_in_bytes = (u4)array->length() * type2aelembytes(type);
switch (type) { switch (type) {
case T_INT : { case T_INT : {
if (Bytes::is_Java_byte_ordering_different()) { if (Bytes::is_Java_byte_ordering_different()) {
WRITE_ARRAY(array, int, u4); WRITE_ARRAY(array, int, u4, length);
} else { } else {
writer->write_raw((void*)(array->int_at_addr(0)), length_in_bytes); writer->write_raw((void*)(array->int_at_addr(0)), length_in_bytes);
} }
...@@ -1102,7 +1189,7 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) { ...@@ -1102,7 +1189,7 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) {
} }
case T_CHAR : { case T_CHAR : {
if (Bytes::is_Java_byte_ordering_different()) { if (Bytes::is_Java_byte_ordering_different()) {
WRITE_ARRAY(array, char, u2); WRITE_ARRAY(array, char, u2, length);
} else { } else {
writer->write_raw((void*)(array->char_at_addr(0)), length_in_bytes); writer->write_raw((void*)(array->char_at_addr(0)), length_in_bytes);
} }
...@@ -1110,7 +1197,7 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) { ...@@ -1110,7 +1197,7 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) {
} }
case T_SHORT : { case T_SHORT : {
if (Bytes::is_Java_byte_ordering_different()) { if (Bytes::is_Java_byte_ordering_different()) {
WRITE_ARRAY(array, short, u2); WRITE_ARRAY(array, short, u2, length);
} else { } else {
writer->write_raw((void*)(array->short_at_addr(0)), length_in_bytes); writer->write_raw((void*)(array->short_at_addr(0)), length_in_bytes);
} }
...@@ -1118,7 +1205,7 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) { ...@@ -1118,7 +1205,7 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) {
} }
case T_BOOLEAN : { case T_BOOLEAN : {
if (Bytes::is_Java_byte_ordering_different()) { if (Bytes::is_Java_byte_ordering_different()) {
WRITE_ARRAY(array, bool, u1); WRITE_ARRAY(array, bool, u1, length);
} else { } else {
writer->write_raw((void*)(array->bool_at_addr(0)), length_in_bytes); writer->write_raw((void*)(array->bool_at_addr(0)), length_in_bytes);
} }
...@@ -1126,7 +1213,7 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) { ...@@ -1126,7 +1213,7 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) {
} }
case T_LONG : { case T_LONG : {
if (Bytes::is_Java_byte_ordering_different()) { if (Bytes::is_Java_byte_ordering_different()) {
WRITE_ARRAY(array, long, u8); WRITE_ARRAY(array, long, u8, length);
} else { } else {
writer->write_raw((void*)(array->long_at_addr(0)), length_in_bytes); writer->write_raw((void*)(array->long_at_addr(0)), length_in_bytes);
} }
...@@ -1138,14 +1225,14 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) { ...@@ -1138,14 +1225,14 @@ void DumperSupport::dump_prim_array(DumpWriter* writer, typeArrayOop array) {
// use IEEE 754. // use IEEE 754.
case T_FLOAT : { case T_FLOAT : {
for (int i=0; i<array->length(); i++) { for (int i = 0; i < length; i++) {
dump_float( writer, array->float_at(i) ); dump_float(writer, array->float_at(i));
} }
break; break;
} }
case T_DOUBLE : { case T_DOUBLE : {
for (int i=0; i<array->length(); i++) { for (int i = 0; i < length; i++) {
dump_double( writer, array->double_at(i) ); dump_double(writer, array->double_at(i));
} }
break; break;
} }
...@@ -1362,8 +1449,6 @@ class VM_HeapDumper : public VM_GC_Operation { ...@@ -1362,8 +1449,6 @@ class VM_HeapDumper : public VM_GC_Operation {
JavaThread* _oome_thread; JavaThread* _oome_thread;
Method* _oome_constructor; Method* _oome_constructor;
bool _gc_before_heap_dump; bool _gc_before_heap_dump;
bool _is_segmented_dump;
jlong _dump_start;
GrowableArray<Klass*>* _klass_map; GrowableArray<Klass*>* _klass_map;
ThreadStackTrace** _stack_traces; ThreadStackTrace** _stack_traces;
int _num_threads; int _num_threads;
...@@ -1382,11 +1467,6 @@ class VM_HeapDumper : public VM_GC_Operation { ...@@ -1382,11 +1467,6 @@ class VM_HeapDumper : public VM_GC_Operation {
void clear_global_dumper() { _global_dumper = NULL; } void clear_global_dumper() { _global_dumper = NULL; }
void clear_global_writer() { _global_writer = NULL; } void clear_global_writer() { _global_writer = NULL; }
bool is_segmented_dump() const { return _is_segmented_dump; }
void set_segmented_dump() { _is_segmented_dump = true; }
jlong dump_start() const { return _dump_start; }
void set_dump_start(jlong pos);
bool skip_operation() const; bool skip_operation() const;
// writes a HPROF_LOAD_CLASS record // writes a HPROF_LOAD_CLASS record
...@@ -1411,16 +1491,6 @@ class VM_HeapDumper : public VM_GC_Operation { ...@@ -1411,16 +1491,6 @@ class VM_HeapDumper : public VM_GC_Operation {
// HPROF_TRACE and HPROF_FRAME records // HPROF_TRACE and HPROF_FRAME records
void dump_stack_traces(); void dump_stack_traces();
// writes a HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT record
void write_dump_header();
// fixes up the length of the current dump record
void write_current_dump_record_length();
// fixes up the current dump record )and writes HPROF_HEAP_DUMP_END
// record in the case of a segmented heap dump)
void end_of_dump();
public: public:
VM_HeapDumper(DumpWriter* writer, bool gc_before_heap_dump, bool oome) : VM_HeapDumper(DumpWriter* writer, bool gc_before_heap_dump, bool oome) :
VM_GC_Operation(0 /* total collections, dummy, ignored */, VM_GC_Operation(0 /* total collections, dummy, ignored */,
...@@ -1429,8 +1499,6 @@ class VM_HeapDumper : public VM_GC_Operation { ...@@ -1429,8 +1499,6 @@ class VM_HeapDumper : public VM_GC_Operation {
gc_before_heap_dump) { gc_before_heap_dump) {
_local_writer = writer; _local_writer = writer;
_gc_before_heap_dump = gc_before_heap_dump; _gc_before_heap_dump = gc_before_heap_dump;
_is_segmented_dump = false;
_dump_start = (jlong)-1;
_klass_map = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<Klass*>(INITIAL_CLASS_COUNT, true); _klass_map = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<Klass*>(INITIAL_CLASS_COUNT, true);
_stack_traces = NULL; _stack_traces = NULL;
_num_threads = 0; _num_threads = 0;
...@@ -1470,35 +1538,23 @@ bool VM_HeapDumper::skip_operation() const { ...@@ -1470,35 +1538,23 @@ bool VM_HeapDumper::skip_operation() const {
return false; return false;
} }
// sets the dump starting position // writes a HPROF_HEAP_DUMP_SEGMENT record
void VM_HeapDumper::set_dump_start(jlong pos) { void DumperSupport::write_dump_header(DumpWriter* writer) {
_dump_start = pos; if (writer->is_open()) {
} writer->write_u1(HPROF_HEAP_DUMP_SEGMENT);
writer->write_u4(0); // current ticks
// writes a HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT record
void VM_HeapDumper::write_dump_header() {
if (writer()->is_open()) {
if (is_segmented_dump()) {
writer()->write_u1(HPROF_HEAP_DUMP_SEGMENT);
} else {
writer()->write_u1(HPROF_HEAP_DUMP);
}
writer()->write_u4(0); // current ticks
// record the starting position for the dump (its length will be fixed up later) // record the starting position for the dump (its length will be fixed up later)
set_dump_start(writer()->current_offset()); writer->set_dump_start(writer->current_offset());
writer()->write_u4(0); writer->write_u4(0);
} }
} }
// fixes up the length of the current dump record // fixes up the length of the current dump record
void VM_HeapDumper::write_current_dump_record_length() { void DumperSupport::write_current_dump_record_length(DumpWriter* writer) {
if (writer()->is_open()) { if (writer->is_open()) {
assert(dump_start() >= 0, "no dump start recorded"); julong dump_end = writer->bytes_written() + writer->bytes_unwritten();
julong dump_len = writer->current_record_length();
// calculate the size of the dump record
julong dump_end = writer()->current_offset();
julong dump_len = (dump_end - dump_start() - 4);
// record length must fit in a u4 // record length must fit in a u4
if (dump_len > max_juint) { if (dump_len > max_juint) {
...@@ -1506,17 +1562,18 @@ void VM_HeapDumper::write_current_dump_record_length() { ...@@ -1506,17 +1562,18 @@ void VM_HeapDumper::write_current_dump_record_length() {
} }
// seek to the dump start and fix-up the length // seek to the dump start and fix-up the length
writer()->seek_to_offset(dump_start()); assert(writer->dump_start() >= 0, "no dump start recorded");
writer()->write_u4((u4)dump_len); writer->seek_to_offset(writer->dump_start());
writer->write_u4((u4)dump_len);
// adjust the total size written to keep the bytes written correct. // adjust the total size written to keep the bytes written correct.
writer()->adjust_bytes_written(-((jlong) sizeof(u4))); writer->adjust_bytes_written(-((jlong) sizeof(u4)));
// seek to dump end so we can continue // seek to dump end so we can continue
writer()->seek_to_offset(dump_end); writer->seek_to_offset(dump_end);
// no current dump record // no current dump record
set_dump_start((jlong)-1); writer->set_dump_start((jlong)-1);
} }
} }
...@@ -1524,33 +1581,23 @@ void VM_HeapDumper::write_current_dump_record_length() { ...@@ -1524,33 +1581,23 @@ void VM_HeapDumper::write_current_dump_record_length() {
// new segment. // new segment.
void VM_HeapDumper::check_segment_length() { void VM_HeapDumper::check_segment_length() {
if (writer()->is_open()) { if (writer()->is_open()) {
if (is_segmented_dump()) { julong dump_len = writer()->current_record_length();
// don't use current_offset that would be too expensive on a per record basis
julong dump_end = writer()->bytes_written() + writer()->bytes_unwritten(); if (dump_len > 2UL*G) {
assert(dump_end == (julong)writer()->current_offset(), "checking"); DumperSupport::write_current_dump_record_length(writer());
julong dump_len = (dump_end - dump_start() - 4); DumperSupport::write_dump_header(writer());
assert(dump_len <= max_juint, "bad dump length");
if (dump_len > HeapDumpSegmentSize) {
write_current_dump_record_length();
write_dump_header();
}
} }
} }
} }
// fixes up the current dump record )and writes HPROF_HEAP_DUMP_END // fixes up the current dump record and writes HPROF_HEAP_DUMP_END record
// record in the case of a segmented heap dump) void DumperSupport::end_of_dump(DumpWriter* writer) {
void VM_HeapDumper::end_of_dump() { if (writer->is_open()) {
if (writer()->is_open()) { write_current_dump_record_length(writer);
write_current_dump_record_length();
// for segmented dump we write the end record writer->write_u1(HPROF_HEAP_DUMP_END);
if (is_segmented_dump()) { writer->write_u4(0);
writer()->write_u1(HPROF_HEAP_DUMP_END); writer->write_u4(0);
writer()->write_u4(0);
writer()->write_u4(0);
}
} }
} }
...@@ -1716,16 +1763,17 @@ void VM_HeapDumper::do_threads() { ...@@ -1716,16 +1763,17 @@ void VM_HeapDumper::do_threads() {
// [HPROF_LOAD_CLASS]* // [HPROF_LOAD_CLASS]*
// [[HPROF_FRAME]*|HPROF_TRACE]* // [[HPROF_FRAME]*|HPROF_TRACE]*
// [HPROF_GC_CLASS_DUMP]* // [HPROF_GC_CLASS_DUMP]*
// HPROF_HEAP_DUMP // [HPROF_HEAP_DUMP_SEGMENT]*
// HPROF_HEAP_DUMP_END
// //
// The HPROF_TRACE records represent the stack traces where the heap dump // The HPROF_TRACE records represent the stack traces where the heap dump
// is generated and a "dummy trace" record which does not include // is generated and a "dummy trace" record which does not include
// any frames. The dummy trace record is used to be referenced as the // any frames. The dummy trace record is used to be referenced as the
// unknown object alloc site. // unknown object alloc site.
// //
// The HPROF_HEAP_DUMP record has a length following by sub-records. To allow // Each HPROF_HEAP_DUMP_SEGMENT record has a length followed by sub-records.
// the heap dump be generated in a single pass we remember the position of // To allow the heap dump be generated in a single pass we remember the position
// the dump length and fix it up after all sub-records have been written. // of the dump length and fix it up after all sub-records have been written.
// To generate the sub-records we iterate over the heap, writing // To generate the sub-records we iterate over the heap, writing
// HPROF_GC_INSTANCE_DUMP, HPROF_GC_OBJ_ARRAY_DUMP, and HPROF_GC_PRIM_ARRAY_DUMP // HPROF_GC_INSTANCE_DUMP, HPROF_GC_OBJ_ARRAY_DUMP, and HPROF_GC_PRIM_ARRAY_DUMP
// records as we go. Once that is done we write records for some of the GC // records as we go. Once that is done we write records for some of the GC
...@@ -1752,15 +1800,9 @@ void VM_HeapDumper::doit() { ...@@ -1752,15 +1800,9 @@ void VM_HeapDumper::doit() {
set_global_dumper(); set_global_dumper();
set_global_writer(); set_global_writer();
// Write the file header - use 1.0.2 for large heaps, otherwise 1.0.1 // Write the file header - we always use 1.0.2
size_t used = ch->used(); size_t used = ch->used();
const char* header; const char* header = "JAVA PROFILE 1.0.2";
if (used > (size_t)SegmentedHeapDumpThreshold) {
set_segmented_dump();
header = "JAVA PROFILE 1.0.2";
} else {
header = "JAVA PROFILE 1.0.1";
}
// header is few bytes long - no chance to overflow int // header is few bytes long - no chance to overflow int
writer()->write_raw((void*)header, (int)strlen(header)); writer()->write_raw((void*)header, (int)strlen(header));
...@@ -1780,8 +1822,8 @@ void VM_HeapDumper::doit() { ...@@ -1780,8 +1822,8 @@ void VM_HeapDumper::doit() {
// this must be called after _klass_map is built when iterating the classes above. // this must be called after _klass_map is built when iterating the classes above.
dump_stack_traces(); dump_stack_traces();
// write HPROF_HEAP_DUMP or HPROF_HEAP_DUMP_SEGMENT // write HPROF_HEAP_DUMP_SEGMENT
write_dump_header(); DumperSupport::write_dump_header(writer());
// Writes HPROF_GC_CLASS_DUMP records // Writes HPROF_GC_CLASS_DUMP records
ClassLoaderDataGraph::classes_do(&do_class_dump); ClassLoaderDataGraph::classes_do(&do_class_dump);
...@@ -1789,9 +1831,9 @@ void VM_HeapDumper::doit() { ...@@ -1789,9 +1831,9 @@ void VM_HeapDumper::doit() {
check_segment_length(); check_segment_length();
// writes HPROF_GC_INSTANCE_DUMP records. // writes HPROF_GC_INSTANCE_DUMP records.
// After each sub-record is written check_segment_length will be invoked. When // After each sub-record is written check_segment_length will be invoked
// generated a segmented heap dump this allows us to check if the current // to check if the current segment exceeds a threshold. If so, a new
// segment exceeds a threshold and if so, then a new segment is started. // segment is started.
// The HPROF_GC_CLASS_DUMP and HPROF_GC_INSTANCE_DUMP are the vast bulk // The HPROF_GC_CLASS_DUMP and HPROF_GC_INSTANCE_DUMP are the vast bulk
// of the heap dump. // of the heap dump.
HeapObjectDumper obj_dumper(this, writer()); HeapObjectDumper obj_dumper(this, writer());
...@@ -1817,9 +1859,8 @@ void VM_HeapDumper::doit() { ...@@ -1817,9 +1859,8 @@ void VM_HeapDumper::doit() {
StickyClassDumper class_dumper(writer()); StickyClassDumper class_dumper(writer());
SystemDictionary::always_strong_classes_do(&class_dumper); SystemDictionary::always_strong_classes_do(&class_dumper);
// fixes up the length of the dump record. In the case of a segmented // fixes up the length of the dump record and writes the HPROF_HEAP_DUMP_END record.
// heap then the HPROF_HEAP_DUMP_END record is also written. DumperSupport::end_of_dump(writer());
end_of_dump();
// Now we clear the global variables, so that a future dumper might run. // Now we clear the global variables, so that a future dumper might run.
clear_global_dumper(); clear_global_dumper();
......
...@@ -1060,7 +1060,7 @@ void VMError::report_and_die() { ...@@ -1060,7 +1060,7 @@ void VMError::report_and_die() {
out.print_raw (cmd); out.print_raw (cmd);
out.print_raw_cr("\" ..."); out.print_raw_cr("\" ...");
if (os::fork_and_exec(cmd, true) < 0) { if (os::fork_and_exec(cmd) < 0) {
out.print_cr("os::fork_and_exec failed: %s (%d)", strerror(errno), errno); out.print_cr("os::fork_and_exec failed: %s (%d)", strerror(errno), errno);
} }
} }
...@@ -1147,7 +1147,7 @@ void VM_ReportJavaOutOfMemory::doit() { ...@@ -1147,7 +1147,7 @@ void VM_ReportJavaOutOfMemory::doit() {
#endif #endif
tty->print_cr("\"%s\"...", cmd); tty->print_cr("\"%s\"...", cmd);
if (os::fork_and_exec(cmd) < 0) { if (os::fork_and_exec(cmd, true) < 0) {
tty->print_cr("os::fork_and_exec failed: %s (%d)", strerror(errno), errno); tty->print_cr("os::fork_and_exec failed: %s (%d)", strerror(errno), errno);
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册