On Sun, 1 Aug 2021 08:17:08 GMT, Thomas Stuefe <stu...@openjdk.org> wrote:

>> Short: this patch makes NMT available in custom-launcher scenarios and 
>> during gtests. It simplifies NMT initialization. It adds a lot of 
>> NMT-specific testing, cleans them up and makes them sideeffect-free.
>> 
>> ---------
>> 
>> NMT continues to be an extremely useful tool for SAP to tackle memory 
>> problems in the JVM.
>> 
>> However, NMT is of limited use due to the following restrictions:
>> 
>> - NMT cannot be used if the hotspot is embedded into a custom launcher 
>> unless the launcher actively cooperates. Just creating and invoking the JVM 
>> is not enough, it needs to do some steps prior to loading the hotspot. This 
>> limitation is not well known (nor, do I believe, documented). Many products 
>> don't do this, e.g., you cannot use NMT with IntelliJ. For us at SAP this 
>> problem limits NMT usefulness greatly since our VMs are often embedded into 
>> custom launchers and modifying every launcher is impossible.
>> - Worse, if that custom launcher links the libjvm *statically* there is just 
>> no way to activate NMT at all. This is the reason NMT cannot be used in the 
>> `gtestlauncher`.
>> - Related to that is that we cannot pass NMT options via `JAVA_TOOL_OPTIONS` 
>> and `-XX:Flags=<file>`.
>> - The fact that NMT cannot be used in gtests is really a pity since it would 
>> allow us to both test NMT itself more rigorously and check for memory leaks 
>> while testing other stuff.
>> 
>> The reason for all this is that NMT initialization happens very early, on 
>> the first call to `os::malloc()`. And those calls happen already during 
>> dynamic C++ initialization - a long time before the VM gets around parsing 
>> arguments. So, regular VM argument parsing is too late to parse NMT 
>> arguments.
>> 
>> The current solution is to pass NMT arguments via a specially prepared 
>> environment variable: `NMT_LEVEL_<PID>=<NMT arguments>`. That environment 
>> variable has to be set by the embedding launcher, before it loads the 
>> libjvm. Since its name contains the PID, we cannot even set that variable in 
>> the shell before starting the launcher.
>> 
>> All that means that every launcher needs to especially parse and process the 
>> NMT arguments given at the command line (or via whatever method) and prepare 
>> the environment variable. `java` itself does this. This only works before 
>> the libjvm.so is loaded, before its dynamic C++ initialization. For that 
>> reason, it does not work if the launcher links statically against the 
>> hotspot, since in that case C++ initialization of the launcher and hotspot 
>> are folded into one phase with no possibility of executing code beforehand.
>> 
>> And since it bypasses argument handling in the VM, it bypasses a number of 
>> argument processing ways, e.g., `JAVA_TOOL_OPTIONS`.
>> 
>> ------
>> 
>> This patch fixes these shortcomings by making NMT late-initializable: it can 
>> now be initialized after normal VM argument parsing, like all other parts of 
>> the VM. This greatly simplifies NMT initialization and makes it work 
>> automagically for every third party launcher, as well as within our gtests.
>> 
>> The glaring problem with late-initializing NMT is the NMT malloc headers. If 
>> we rule out just always having them (unacceptable in terms of memory 
>> overhead), there is no safe way to determine, in os::free(), if an 
>> allocation came from before or after NMT initialization ran, and therefore 
>> what to do with its malloc headers. For a more extensive explanation, please 
>> see the comment block `nmtPreInit.hpp` and the discussion with @kimbarrett 
>> and @zhengyu123 in the JBS comment section.
>> 
>> The heart of this patch is a new way to track early, pre-NMT-init 
>> allocations. These are tracked via a lookup table. This was a suggestion by 
>> Kim and it worked out well.
>> 
>> Changes in detail:
>> 
>> - pre-NMT-init handling:
>>      - the new files `nmtPreInit.hpp/cpp` take case of NMT pre-init 
>> handling. They contain a small global lookup table managing C-heap blocks 
>> allocated in the pre-NMT-init phase.
>>      - `os::malloc()/os::realloc()/os::free()` defer to this code before 
>> doing anything else.
>>      - Please see the extensive comment block at the start of 
>> `nmtPreinit.hpp` explaining the details.
>> 
>> - Changes to NMT:
>>      - Before, NMT initialization was spread over two phases, `initialize()` 
>> and `late_initialize()`. Those were merged into one and simplified - there 
>> is only one initialization now which happens after argument parsing.
>>      - Minor changes were needed for the `NMT_TrackingLevel` enum - to 
>> simplify code, I changed NMT_unknown to be numerically 0. A new comment 
>> block in `nmtCommon.hpp` now clearly specifies what's what, including 
>> allowed level state transitions.
>>      - New utility functions to translate tracking level from/to strings 
>> added to `NMTUtil`
>>      - NMT has never been able to handle virtual memory allocations before 
>> initialization, which is fine since os::reserve_memory() is not called 
>> before VM parses arguments. We now assert that.
>>      - All code outside the VM handling NMT initialization (eg. libjli) has 
>> been removed, as has the code testing it.
>> 
>> - Gtests:
>>      - Some existing gtests had to be modified: before, they all changed 
>> global state (turning NMT on/off) before testing. This is not allowed 
>> anymore, to keep NMT simple. Also, this pattern disturbed other tests.
>>      - The new way to test is to passively check whether NMT has been 
>> switched on or off, and do tests accordingly: if on, full tests, if off, 
>> test just what makes sense in off-state. That does not disturb neighboring 
>> tests, gives us actually better coverage all around.
>>      - It is now possible to start the gtestlauncher with NMT on! Which 
>> additionally gives us good coverage.
>>      - To actually do gtests with NMT - since it's disabled by default - we 
>> now run NMT-enabled gtests as part of the hotspot jtreg NMT wrapper. This 
>> pattern we have done for a number of other facitilites, see all the tests in 
>> test/hotspot/jtreg/gtest.. . It works very well.
>>      - Finally, a new gtest has been written to test the NMT preinit lookup 
>> map in isolation, placed in `gtest/nmt/test_nmtpreinitmap.cpp`.
>>      
>> - jtreg:
>>      - A new test has been added, `runtime/NMT/NMTInitializationTest.java`, 
>> testing NMT initialization in the face of many many VM arguments.
>> 
>> -------------
>> 
>> Tests:
>> - ran manually all new tests on 64-bit and 32-bit Linux
>> - GHAs
>> - The patch has been active in SAPs test systems for a while now.
>
> Thomas Stuefe has updated the pull request incrementally with six additional 
> commits since the last revision:
> 
>  - feedback zhengyu
>  - feeback Coleen/Kim (constness fix)
>  - Feedback David
>  - Add test to test for non-java launcher support of NMT
>  - move all test code to gtest
>  - actually free blocks freed in pre-init phase

This looks good.  Thanks for fixing the mysterious (to me) cast.

src/hotspot/share/services/nmtPreInit.cpp line 207:

> 205: static TestAllocations g_test_allocations; // make this an automatic 
> object to let ctor run during in C++ dynamic initialization.
> 206: #endif // ASSERT
> 207: 

Oh, good.  I was hoping this didn't need to be an in-jvm test.

-------------

Marked as reviewed by coleenp (Reviewer).

PR: https://git.openjdk.java.net/jdk/pull/4874

Reply via email to