> We see C-heap leaking, originating in C2:
>
> 1434777 [0x00007f58214af461] stringStream::stringStream(unsigned long)+0x55
> 1434778 [0x00007f5820c6db3e]
> Compile::PrintInliningBuffer::PrintInliningBuffer()+0x6c
> 1434779 [0x00007f5820c724f1]
> GrowableArrayWithAllocator<Compile::PrintInliningBuffer,
> GrowableArray<Compile::PrintInliningBuffer> >::grow(int)+0x163
> 1434780 [0x00007f5820c7160e]
> GrowableArrayWithAllocator<Compile::PrintInliningBuffer,
> GrowableArray<Compile::PrintInliningBuffer> >::insert_before(int,
> Compile::PrintInliningBuffer const&)+0x82
> 1434781 [0x00007f5820c6aaf2] Compile::print_inlining_push()+0x70
>
> accumulating over the course of days to some hundred MB at a customer site
> where inline tracing was active.
>
>
> Analysis:
>
>
> To build up a comprehensive inlining trace, c2 keeps trace snippets in an
> internal list and assembles them at print time. These are stringStream's,
> contained in PrintInliningBuffer:
>
> GrowableArray<PrintInliningBuffer>* _print_inlining_list;
> ...
> class PrintInliningBuffer : public ResourceObj {
> private:
> CallGenerator* _cg;
> stringStream* _ss;
>
> public:
> PrintInliningBuffer()
> : _cg(NULL) {
> _ss = new stringStream();
> }
>
> void freeStream() {
> _ss->~stringStream(); _ss = NULL; }
> ...
> };
>
> With [JDK-8224193](https://bugs.openjdk.java.net/browse/JDK-8224193),
> stringStream was changed to use C-heap instead of ResourceArea for its
> internal buffer. This means one cannot just omit stringStream destruction
> anymore - even where stringStream objects themselves live in RA, their
> internal buffers don't, they live in C-Heap. To clean up properly,
> ~stringStream() must be called.
>
> Those `PrintInliningBuffer` objects are kept _by value_ in a GrowableArray
> `Compile::_print_inlining_list`. Normally, if an object is kept by value, it
> needs to implement correct copy- and destruction-semantics.
> PrintInliningBuffer does not do that and instead relies on manual cleanup
> (`PrintInliningBuffer::freeStream()`) - I assume the authors did not want to
> deal with reference counting the contained stringStream on copying.
>
> That however is a problem because GrowableArray creates internally temporary
> objects of the item type to pre-populate its array - its whole capacity -
> when it grows:
>
>
> template <typename E, typename Derived>
> void GrowableArrayWithAllocator<E, Derived>::grow(int j) {
>
> ...
> for ( ; i < this->_len; i++) ::new ((void*)&newData[i])
> E(this->_data[i]);
> for ( ; i < this->_max; i++) ::new ((void*)&newData[i]) E(); <<<<<<<
> for (i = 0; i < old_max; i++) this->_data[i].~E();
> ...
> }
>
> this it does for the whole new capacity, which means more objects get created
> than have been added; if the caller does not fill the GrowableArray up to its
> capacity, it never knows about the excess objects created. This is normally
> not a problem since all these objects are destructed by GrowableArray in
> `void GrowableArrayWithAllocator<E, Derived>::clear_and_deallocate()`. But
> since PrintInliningBuffer does not implement a destructor, this has no effect.
>
> PrintInliningBuffer instead relies on manual cleanup of the stringStreams: at
> the end of the compile phase, it calls `PrintInliningBuffer::freeStream()` on
> all buffers it thinks are contained in the array:
>
> assert(_print_inlining_list != NULL, "process_print_inlining should be
> called only once.");
> for (int i = 0; i < _print_inlining_list->length(); i++) {
> ss.print("%s", _print_inlining_list->adr_at(i)->ss()->as_string());
> _print_inlining_list->at(i).freeStream();
> }
>
> but this of course leaves the excess objects untouched (objects whose index
> is array length >= index >= array capacity).
>
> -----------
>
> There are several ways to fix this:
>
> 1) implement correct destructor- and copy semantics for PrintInliningBuffer.
> This would work but is not optimal since we would still pay for creation of
> unnecessary PrintInliningBuffer objects when the array is prepopulated on
> grow()
>
> 2) delay construction of `PrintInliningBuffer::_ss` until the stream is
> actually used for writing into. This would would mean we have to check the
> stringStream for NULL before using it.
>
> 3) Just let the PrintInliningBuffer live in C-heap instead of the RA. Its
> only a small shell anywhere now that the stringStream buffer lives in C-heap.
> Instead, let PrintInliningBuffer contain the stringStream as a member,
> allocate PrintInliningBuffer from C-heap, and change the list to contain
> pointers to PrintInliningBuffer, not the object itself. This removes all that
> unnecessary copying, removes creation of temporary objects, and simplifies
> the code. Having to free those objects is no big deal since we free the
> stringStream objects manually anyway.
>
> I went with (3). I also decreased the default stringStream buffer size for
> this scenario since when testing I found out that many trace snippets are
> below 128 bytes.
>
> Note that (3) is not only simpler, but also more efficient: many of the
> PrintInliningBuffer objects are inserted into the middle of the
> _print_inlining_list; GrowableArray does shift all higher items up to provide
> space for the new item. If those items are simple pointers instead of whole
> objects, less memory is moved around.
>
> Tests:
> - github actions
> - Nightlies at SAP
> - I manually tested the patch and compared the NMT output of a tracing
> scenario to verify that the leak went away and no other numbers changed
>
> Thanks, Thomas
Thomas Stuefe has updated the pull request incrementally with one additional
commit since the last revision:
change return parameter of Compile::print_inlining_current()
-------------
Changes:
- all: https://git.openjdk.java.net/jdk/pull/2063/files
- new: https://git.openjdk.java.net/jdk/pull/2063/files/4045f2f8..b52b140b
Webrevs:
- full: https://webrevs.openjdk.java.net/?repo=jdk&pr=2063&range=01
- incr: https://webrevs.openjdk.java.net/?repo=jdk&pr=2063&range=00-01
Stats: 2375 lines in 4 files changed: 2365 ins; 0 del; 10 mod
Patch: https://git.openjdk.java.net/jdk/pull/2063.diff
Fetch: git fetch https://git.openjdk.java.net/jdk pull/2063/head:pull/2063
PR: https://git.openjdk.java.net/jdk/pull/2063