On Thu, 22 Jul 2021 14:58:47 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. Hi Thomas, I had a look through this and it seems reasonable, but I'm not familiar enough with the details to approve at this stage. A few nits below. Thanks, David src/hotspot/share/services/nmtCommon.hpp line 127: > 125: } > 126: > 127: // parses the tracking level from a string. Returns NMT_unknown if Nit: start sentence with capital P src/hotspot/share/services/nmtCommon.hpp line 131: > 129: static NMT_TrackingLevel parse_tracking_level(const char* s); > 130: > 131: // returns textual representation of a tracking level. Nit: start sentence with capital R src/hotspot/share/services/nmtPreInit.hpp line 114: > 112: // > 113: // We use a basic open hashmap, dimensioned generously - hash collisions > should be very rare. > 114: // The table is customized for holding malloced pointers. One main > point of this map is that we do Nit: double-space at line start (and for rest of comment block) src/hotspot/share/services/nmtPreInit.hpp line 140: > 138: > 139: // table_size: keep table size a prime and the hash function simple; > this > 140: // seems to give a good distribution for malloce'd pointers on all > our libc variants. typo: malloce'd src/hotspot/share/services/nmtPreInit.hpp line 268: > 266: add_to_map(a); > 267: (*rc) = a->payload(); > 268: _num_mallocs_pre ++; style nit: no space with unary operator src/java.base/share/native/libjli/java.c line 807: > 805: */ > 806: static void > 807: SetJvmEnvironment(int argc, char **argv) { This doesn't seem to do anything any more. test/hotspot/gtest/nmt/test_nmtpreinitmap.cpp line 2: > 1: /* > 2: * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights > reserved. New file should only have single, current, copyright year ------------- PR: https://git.openjdk.java.net/jdk/pull/4874