eclipse / omr

Eclipse OMR™ Cross platform components for building reliable, high performance language runtimes
http://www.eclipse.org/omr
Other
940 stars 394 forks source link

Region created but destructor not called? #6787

Open ddxxdd-code opened 1 year ago

ddxxdd-code commented 1 year ago

Dear developers,

I’m currently looking at memory usage by OpenJ9 JVM during JIT compilation. I track the usage of memory allocated by segment providers by regions and allocations.

I found some regions in OMR that are created but their destructors never be called. These regions call allocate() and hold memory from segment provider until the end of a compilation. I feel this might imply possible memory leak.

Here is the list of such regions by where they are created: https://github.com/eclipse/omr/blob/556374a6151cdaac3570316a3bc00c3fd7d9c56f/compiler/infra/OMRCfg.hpp#L101 https://github.com/eclipse/omr/blob/556374a6151cdaac3570316a3bc00c3fd7d9c56f/compiler/infra/OMRCfg.hpp#L108 https://github.com/eclipse/omr/blob/556374a6151cdaac3570316a3bc00c3fd7d9c56f/compiler/optimizer/OMROptimizationManager.cpp#L270 https://github.com/eclipse/omr/blob/556374a6151cdaac3570316a3bc00c3fd7d9c56f/compiler/compile/OMRCompilation.cpp#L321

AlexeyKhrabrov commented 1 year ago

@mpirvu @jdmpapin FYI

jdmpapin commented 1 year ago

The node region is used to allocate nodes, which may live until the end of the compilation, so that one is no problem.

Each CFG has an "internal" and a "structure" memory region. The main CFG lives until the end of the compilation, so that one is fine as well. Inlining creates temporary CFGs for methods under consideration during estimateCodeSize() and also for each actually inlined method during physical inlining. The latter CFGs actually get merged into the main CFG, so their nodes and edges within the "internal" region also live (in general...) until the end of the compilation. The former CFG objects though are allocated within a stack region, and then forgotten, unnecessarily holding on to their regions (and therefore segments). In the compilations I've looked at, there hasn't been much memory tied up in this way though, since most inlined methods have CFGs that are small enough to fit within the region's embedded 4kB buffer.

I've written some changes related to CFG here - that I just haven't yet had a chance to contribute - which have the following effects:

  1. Register inliner's temporary CFGs to be destroyed. This was the motivation for #6648.
  2. For inlined method CFGs that will be merged into the main CFG, reuse the main CFG's regions instead of creating new ones.

In compilations with a lot of inlining, these changes will dramatically reduce the number of CFG-related regions, which makes my (also yet-to-be-contributed) region tracing/visualization much more efficient and less misleading. (The large number of CFG-related regions seemed to be much more worrisome than it really was.)

The one that really surprises me here is the stack region in performChecks(). How do we manage not to destroy that one?

ddxxdd-code commented 1 year ago

Thanks for detailed explanations for each of the cases!

For that one in performChecks(), it comes from the below hot compilation:

  • (hot) com/ibm/ws/tcpchannel/internal/WorkQueueManager.attemptIO(Lcom/ibm/ws/tcpchannel/internal/TCPBaseRequestContext;Z)Z @ 00007F4520A1BAC8-00007F4520A23A1D OrdinaryMethod G Q_SZ=4805 Q_SZI=3 QW=32612 j9m=000000000080DE38 bcsz=796 OSR time=30369763us mem=[region=107840 system=114688]KB compThreadID=2 CpuLoad=0%(0%avg) JvmCpu=74% queueTime=94031585us

The backtrace of this region's constructor: In the format of <file>:<line>; <function name>, back from constructor of the region:

/omr/compiler/env/StackMemoryRegion.cpp:30; TR::StackMemoryRegion::StackMemoryRegion(TR_Memory&) /omr/compiler/optimizer/OMROptimizationManager.cpp:270; OMR::OptimizationManager::performChecks() /omr/compiler/env/Region.hpp:145; OMR::CFG::invalidateStructure()

The tracked source of this region is from call of OMR::CFG::invalidateStructure(), which results in call to reset(): https://github.com/eclipse/omr/blob/556374a6151cdaac3570316a3bc00c3fd7d9c56f/compiler/env/Region.hpp#L145

I've also checked if this is an issue happens by accident like the compilation is terminated before it completes, but it appeared in all of the four hot compilations I collected data from and they use segment provider to allocate a total of 589824, 262144, 131072, and 131072 bytes in this region. Generally hundreds of KB is caused by this region. Compared to total usage of usually tens or hundred of MB for one compilation, this region's contribution is small.

My current track depth for region's constructor is only three levels back in the stack, I feel I need to extend tracked depth to find the real source in this case.

Will get back to this case after I increase the backtrace depth. Thank you!

jdmpapin commented 1 year ago

Regardless of the calling context, it shouldn't be possible to exit performChecks() without destroying the stack region. Seems like there must be a bug somewhere