New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8262386: resourcehogs/serviceability/sa/TestHeapDumpForLargeArray.java timed out #2803
Conversation
👋 Welcome back lzang! A progress list of the required criteria for merging this PR into |
Webrevs
|
The fix is fairly big and could use some explaining. Can you provide a bit more detail on what the bug is? In the CR you mention it is due to the combination of a very large array and the segmented heap dump support, but don't really explain what the actual bug is. You can add this detail to the CR rather than here. Also can explain your solution, especially this new |
Hi Chris, Thanks for your review, I found there is a potential risk for huge object dumping with this PR, I would work out an update and then add the explaination in the CR. BRs, |
Thanks for the explanation in the CR. That helps a lot. I didn't have time to get through the review today, but will do so tomorrow. |
Hi Chris, BRs, |
Update a new patch that reduce the memory consumption issue.
This could cause large memory consumption because the old buffer data are copied, and also the old buffer can not be "free" until next GC. For example, if the internel buffer's length is 1MB, when it is full, a new 2MB buffer is allocated so there is actually 3MB memory taken (old buffer + new buffer). And in this case, for the ~4GB large array, it keeps generating new buffers and do copying, which takes both CPU and memory intensively and cause the timeout issue. This patch optimize it by creating a array list of byte[]. when old buffer is full, it saved into the list and the new one is created and used as the internal buffer. In this case, the above example takes 2MB(1MB for old, saved in the list; and 1MB for the new buffer) Together with the "write through" mode introduced in this PR, by which all arrays are write through to underlying stream and hence no extra buffer requried. The PR could help fix the LargeArray issue and also save memory. Thanks! |
This reverts commit 70e43dd.
As discussed in CR https://bugs.openjdk.java.net/browse/JDK-8262386, the byte[] list is much more like an optimization. Revert it in the PR and I will create a separate CR and PR for it. Thanks, |
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java
Outdated
Show resolved
Hide resolved
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java
Outdated
Show resolved
Hide resolved
// Now the total size of data to dump is known and can be filled to segment header. | ||
// Enable write-through mode to avoid internal buffer copies. | ||
if (useSegmentedHeapDump) { | ||
long longBytes = length * typeSize + headerSize; | ||
int bytesToWrite = (int) (longBytes); | ||
hprofBufferedOut.fillSegmentSizeAndEnableWriteThrough(bytesToWrite); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me this is the key part of the fix, and all other changes and driven by this change. What I don't understand is why enabling writeThrough
is done here in calculateArrayMaxLength()
, especially since this same code might be execute more than once for the same segment (thus "enabling" writeThrough
when it is already enabled). What is the actual trigger for wanting writeThrough
mode? Is it really just seeing an array for the first time in a segment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dear Chris,
What I don't understand is why enabling
writeThrough
is done here incalculateArrayMaxLength()
This is the place where the array size could be calculated by the writer before the array element data are written. So there is no need to cache the array in the internal buffer of segmentedOutputStream.
especially since this same code might be execute more than once for the same segment (thus "enabling"
writeThrough
when it is already enabled).
IMO, it is not possible to execute write-through multiple times. in fillSegmentSizeAndEnableWriteThrough()
there is a flush that will write all buffered data in to underlying output stream first and then start write-through mode.
And from the implementation in
jdk/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/AbstractHeapGraphWriter.java
Line 62 in c484d89
writeHeapRecordPrologue(); |
the method
writeHeapRecordEpilogue()
would be called for every object iterated. And in this method, the exitSegmentMode()
will be called which will disable write-through mode.
Briefly, the segment could contain multiple objects. But the array object would be treated as a new standalone segment, which is written to the underlying output stream directly by write-through mode.
What is the actual trigger for wanting writeThrough mode? Is it really just seeing an array for the first time in a segment?
The root cause for requiring writeThrough mode is to avoid caching data in the internal buffer of SegmentedOutputStream
, which would cause memory consumption and also performance issues, which is the also the root cause of this bug.
Moreover, the reason that need to "cache data" in internal buffer before data written to file is that current implementation of SA heap dumper need to fill the size slot in segment header only after the segment data are written. Which is hard to do after introducing gzipped heap dump because the header and the data are compressed when written to file and not easy to be modified.
However, the current implementation of dumping array object first calculates the array size, so the size of segment data is known when creating segment header. And this fix uses that info to avoid cache array data.
Hi Lin, One concern I have is the naming used in the fix.
Why 'longBytes' ? The scope of this local variable is very short, and it is clear that its type is long. Thanks, |
One more question.
Does the check for "!writeThrough" at L1368 means there is no need to write the segment header (as at L1371)? |
Dear Serguei,
The "write-through" mode requires filling the segment size in segment header before object contents are scanned and written, And it is only in method
IMO, it is better to dump all array object in On the other side, the specific issue described at JDK-8262386 could also be fixed if enable write-through mode only when array size is larger than maxBytes calculated in
Yes, The Thanks! |
I guess I don't understand why you would want write-through for small arrays but not large objects. I also have to admit I don't fully grasp the purpose of "segment mode". I see the docs say:
But all this seems to be doing is grouping the HPROF_HEAP_DUMP records into an array rather than having them interspersed with other types of records. How does this help, and why would this mode not always be enabled? Also I'm not so sure the above documentation is fully accurate. It looks like both HPROF_HEAP_DUMP_SEGMENT and HPROF_HEAP_DUMP_END are followed by a 4-byte size and 4-byte timestamp, although the timestamp seems to always be 0, and the size for END is also always 0. |
Dear Chris,
I think it is because that the current implementation does not have code which could calculate the object size before scan it. but it has the logic for calculate array length (
I think the original pupose of SEGMENT heap dump is to handle the case for large heap. In the hprof format spec, the size slot of the heap dump is 32bit, which means it has limitation of dump 4GB used heap. so use segmental dump could help resolve the problem. And IMO the reason for not always enable it is because every segment has a header and tail, which may introduce extra memory, althogh it is not much.
My understand from code is that 4-byte size and 4-byte timestamp only exist after HPROF_HEAP_DUMP_SEGMENT, and the HPROF_HEAP_DUMP_END actually is the end of the segment. So I think the specification is not accurate. Thanks, |
Can't you just call
Ok. So |
Dear Chris,
Yes, I can use it, then I think there is no need to use segmental heap dump when heap used bytes is not large than 4G. so there may be lots of code change. do you think it is ok to include change in this bug fix? or I just keep the segmental heap dump codes and use the write through mode for object dump in this fix.
Yes, I think so. Thanks, |
Dear @plummercj, BRs, |
If your rewrite has the side affect of fixing this issue, what I would suggest is a new CR and PR that just address the rewrite, especially since this PR has a lot of history that I think you are saying will pretty much become obsolete, and therefore is just noise to anyone wanting to review your changes. In the new PR you can note that the changes will also address 8262386, and you can include an |
/integrate |
Dear All, |
@linzang This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration! |
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java
Outdated
Show resolved
Hide resolved
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java
Show resolved
Hide resolved
@@ -1354,7 +1364,7 @@ public SegmentedOutputStream(OutputStream out) { | |||
*/ | |||
@Override | |||
public synchronized void write(int b) throws IOException { | |||
if (segmentMode) { | |||
if (segmentMode && !unbufferedMode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear why the condition !unbufferedMode
is additionally checked here and several places below. The SegmentedOutputStream
constructor always sets false
to theunbufferedMode
field at L1344. Is it possible that segmentMode
can be also unbuffered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unbufferedMode
is set to true at L1498, and it is use when writing array data. The purpose is to write the data directly to the file instead of buffering them first, it can avoid memory copy and also avoid timeout when dumping large array (because the buffer must contains the whole segment before it is written to file, to avoid data pollution for compressed dump, and if the buffer is not enough, a memory copy is conducted)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, thanks.
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/HeapHprofBinWriter.java
Outdated
Show resolved
Hide resolved
Dear Serguei, Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lin,
The fix looks good to me.
Thank you for your patience!
Serguei
Hi Serguei (@sspitsyn ), Hi Chris (@plummercj ), @plummercj @sspitsyn , I will tag this PR with /integrate, and may need your help to sponsor it when it is ready. Thanks! BRs, |
/integrate |
/sponsor |
Going to push as commit 46684a4.
Your commit was automatically rebased without conflicts. |
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.java.net/jdk pull/2803/head:pull/2803
$ git checkout pull/2803
Update a local copy of the PR:
$ git checkout pull/2803
$ git pull https://git.openjdk.java.net/jdk pull/2803/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 2803
View PR using the GUI difftool:
$ git pr show -t 2803
Using diff file
Download this PR as a diff file:
https://git.openjdk.java.net/jdk/pull/2803.diff