Hi Thomas,

On 26/07/2021 10:15 pm, Thomas Stuefe 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.

Before looking at this, have you checked the startup performance impact?

Thanks,
David
-----

---------

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.

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

Commit messages:
  - NMT late init, hashmap variant

Changes: https://git.openjdk.java.net/jdk/pull/4874/files
  Webrev: https://webrevs.openjdk.java.net/?repo=jdk&pr=4874&range=00
   Issue: https://bugs.openjdk.java.net/browse/JDK-8256844
   Stats: 1616 lines in 22 files changed: 1227 ins; 346 del; 43 mod
   Patch: https://git.openjdk.java.net/jdk/pull/4874.diff
   Fetch: git fetch https://git.openjdk.java.net/jdk pull/4874/head:pull/4874

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

Reply via email to