JDK-8259710 : Inlining trace leaks memory
  • Type: Bug
  • Component: hotspot
  • Sub-Component: compiler
  • Affected Version: 11,16,17
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2021-01-13
  • Updated: 2021-03-15
  • Resolved: 2021-01-25
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 17
11.0.12-oracleFixed 17 b07Fixed
Related Reports
Relates :  
Description
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, 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).


Comments
11u Fix Request Backporting this patch eliminates a memory leak with the PrintInlining command line option. While the fix is not critical, it could still prove valuable for diagnostic purposes. It's also required for parity. Patch does not apply cleanly to 11u and requires adjustments. 11u RFR: https://mail.openjdk.java.net/pipermail/jdk-updates-dev/2021-March/005299.html
12-03-2021

Changeset: ca20c63c Author: Thomas Stuefe <stuefe@openjdk.org> Date: 2021-01-25 10:50:39 +0000 URL: https://git.openjdk.java.net/jdk/commit/ca20c63c
25-01-2021

ILW = Memory leak, with PrintInlining enabled (debug option), no workaround but disable PrintInlining = HLM = P3
14-01-2021

I am not sure whether pre-constructing all items for the whole array capacity makes really sense. It may be better to just pre-alloc the needed space and leave it uninitialized until the items are added. But that would be another RFE.
13-01-2021