From a5edc79220300bce7952feaacf28a832306884d8 Mon Sep 17 00:00:00 2001
From: eapen <zhangyipeng7@huawei.com>
Date: Mon, 12 Dec 2022 19:28:28 +0800
Subject: [PATCH 14/33] I68TO2: 8293114: GC should trim the native heap,8136854：G1 ConcurrentG1RefineThread::stop delays JVM shutdown for >150ms
---
 hotspot/src/os/aix/vm/os_aix.cpp                   |   5 +
 hotspot/src/os/bsd/vm/os_bsd.cpp                   |   5 +
 hotspot/src/os/linux/vm/os_linux.cpp               | 149 ++++++-
 hotspot/src/os/linux/vm/os_linux.hpp               |  57 ++-
 hotspot/src/os/linux/vm/trimCHeapDCmd.cpp          |  59 +--
 hotspot/src/os/windows/vm/os_windows.cpp           |   4 +
 .../vm/gc_implementation/g1/g1CollectedHeap.cpp    |   6 +
 .../parallelScavenge/parallelScavengeHeap.cpp      |   3 +
 .../parallelScavenge/psParallelCompact.cpp         |   6 +
 .../shared/concurrentGCThread.cpp                  |   8 +-
 .../shared/concurrentGCThread.hpp                  |   6 +-
 .../gc_implementation/shared/gcTrimNativeHeap.cpp  | 246 ++++++++++++
 .../gc_implementation/shared/gcTrimNativeHeap.hpp  |  66 ++++
 hotspot/src/share/vm/memory/genCollectedHeap.cpp   |   6 +
 hotspot/src/share/vm/memory/sharedHeap.cpp         |   6 +
 hotspot/src/share/vm/runtime/globals.hpp           |  10 +
 hotspot/src/share/vm/runtime/init.cpp              |   5 +-
 hotspot/src/share/vm/runtime/java.cpp              |   3 +
 hotspot/src/share/vm/runtime/os.hpp                |  11 +
 .../src/share/vm/utilities/globalDefinitions.hpp   |   3 +
 hotspot/test/gc/TestTrimNative.java                | 435 +++++++++++++++++++++
 .../test/serviceability/dcmd/TrimLibcHeapTest.java |   7 +-
 22 files changed, 994 insertions(+), 112 deletions(-)
 create mode 100644 hotspot/src/share/vm/gc_implementation/shared/gcTrimNativeHeap.cpp
 create mode 100644 hotspot/src/share/vm/gc_implementation/shared/gcTrimNativeHeap.hpp
 create mode 100644 hotspot/test/gc/TestTrimNative.java

diff --git a/hotspot/src/os/aix/vm/os_aix.cpp b/hotspot/src/os/aix/vm/os_aix.cpp
index b078bee..519b085 100644
--- a/hotspot/src/os/aix/vm/os_aix.cpp
+++ b/hotspot/src/os/aix/vm/os_aix.cpp
@@ -5266,3 +5266,8 @@ void TestReserveMemorySpecial_test() {
   // No tests available for this platform
 }
 #endif
+
+// stubbed-out trim-native support
+bool os::can_trim_native_heap() { return false; }
+bool os::should_trim_native_heap() { return false; }
+bool os::trim_native_heap(os::size_change_t* rss_change) { return false; }
\ No newline at end of file
diff --git a/hotspot/src/os/bsd/vm/os_bsd.cpp b/hotspot/src/os/bsd/vm/os_bsd.cpp
index 340334c..85e2861 100644
--- a/hotspot/src/os/bsd/vm/os_bsd.cpp
+++ b/hotspot/src/os/bsd/vm/os_bsd.cpp
@@ -4899,3 +4899,8 @@ void TestReserveMemorySpecial_test() {
   // No tests available for this platform
 }
 #endif
+
+// stubbed-out trim-native support
+bool os::can_trim_native_heap() { return false; }
+bool os::should_trim_native_heap() { return false; }
+bool os::trim_native_heap(os::size_change_t* rss_change) { return false; }
\ No newline at end of file
diff --git a/hotspot/src/os/linux/vm/os_linux.cpp b/hotspot/src/os/linux/vm/os_linux.cpp
index 099dafa..abf2031 100644
--- a/hotspot/src/os/linux/vm/os_linux.cpp
+++ b/hotspot/src/os/linux/vm/os_linux.cpp
@@ -153,8 +153,28 @@ const char * os::Linux::_libpthread_version = NULL;
 pthread_condattr_t os::Linux::_condattr[1];

 #ifdef __GLIBC__
-os::Linux::mallinfo_func_t os::Linux::_mallinfo = NULL;
-os::Linux::mallinfo2_func_t os::Linux::_mallinfo2 = NULL;
+// We want to be runnable with both old and new glibcs.
+// Old glibcs offer mallinfo(). New glibcs deprecate mallinfo() and offer mallinfo2()
+// as replacement. Future glibc's may remove the deprecated mallinfo().
+// Therefore we may have one, both, or possibly neither (?). Code should tolerate all
+// cases, which is why we resolve the functions dynamically. Outside code should use
+// the Linux::get_mallinfo() utility function which exists to hide this mess.
+struct glibc_mallinfo {
+  int arena;
+  int ordblks;
+  int smblks;
+  int hblks;
+  int hblkhd;
+  int usmblks;
+  int fsmblks;
+  int uordblks;
+  int fordblks;
+  int keepcost;
+};
+typedef struct glibc_mallinfo (*mallinfo_func_t)(void);
+typedef struct os::Linux::glibc_mallinfo2 (*mallinfo2_func_t)(void);
+static mallinfo_func_t g_mallinfo = NULL;
+static mallinfo2_func_t g_mallinfo2 = NULL;
 #endif // __GLIBC__

 static jlong initial_time_count=0;
@@ -2348,23 +2368,21 @@ void os::Linux::print_process_memory_info(outputStream* st) {
   // Print glibc outstanding allocations.
   // (note: there is no implementation of mallinfo for muslc)
 #ifdef __GLIBC__
-  size_t total_allocated = 0;
   bool might_have_wrapped = false;
-  if (_mallinfo2 != NULL) {
-    struct glibc_mallinfo2 mi = _mallinfo2();
-    total_allocated = mi.uordblks;
-  } else if (_mallinfo != NULL) {
-    // mallinfo is an old API. Member names mean next to nothing and, beyond that, are int.
-    // So values may have wrapped around. Still useful enough to see how much glibc thinks
-    // we allocated.
-    struct glibc_mallinfo mi = _mallinfo();
-    total_allocated = (size_t)(unsigned)mi.uordblks;
-    // Since mallinfo members are int, glibc values may have wrapped. Warn about this.
-    might_have_wrapped = (info.vmrss * K) > UINT_MAX && (info.vmrss * K) > (total_allocated + UINT_MAX);
-  }
-  if (_mallinfo2 != NULL || _mallinfo != NULL) {
-    st->print_cr("C-Heap outstanding allocations: " SIZE_FORMAT "K%s",
-                 total_allocated / K,
+  glibc_mallinfo2 mi;
+  mallinfo_retval_t mirc = os::Linux::get_mallinfo(&mi);
+  if (mirc != os::Linux::error) {
+    size_t total_allocated = mi.uordblks + mi.hblkhd;
+    size_t free_retained = mi.fordblks;
+#ifdef _LP64
+    // If all we had is old mallinf(3), the values may have wrapped. Since that can confuse readers
+    // of this output, print a hint.
+    // We do this by checking virtual size of the process: if that is <4g, we could not have wrapped.
+    might_have_wrapped = (mirc == os::Linux::ok_but_possibly_wrapped) &&
+                         ((info.vmsize * K) > UINT_MAX);
+#endif
+    st->print_cr("C-Heap outstanding allocations: " SIZE_FORMAT "K, retained: " SIZE_FORMAT "K%s",
+                 total_allocated / K, free_retained / K,
                  might_have_wrapped ? " (may have wrapped)" : "");
   }

@@ -5187,8 +5205,8 @@ void os::init(void) {
   Linux::initialize_system_info();

 #ifdef __GLIBC__
-  Linux::_mallinfo = CAST_TO_FN_PTR(Linux::mallinfo_func_t, dlsym(RTLD_DEFAULT, "mallinfo"));
-  Linux::_mallinfo2 = CAST_TO_FN_PTR(Linux::mallinfo2_func_t, dlsym(RTLD_DEFAULT, "mallinfo2"));
+  g_mallinfo = CAST_TO_FN_PTR(mallinfo_func_t, dlsym(RTLD_DEFAULT, "mallinfo"));
+  g_mallinfo2 = CAST_TO_FN_PTR(mallinfo2_func_t, dlsym(RTLD_DEFAULT, "mallinfo2"));
 #endif // __GLIBC__

   // _main_thread points to the thread that created/loaded the JVM.
@@ -6820,3 +6838,94 @@ void TestReserveMemorySpecial_test() {
 }

 #endif
+
+#ifdef __GLIBC__
+os::Linux::mallinfo_retval_t os::Linux::get_mallinfo(glibc_mallinfo2* out) {
+  if (g_mallinfo2) {
+    glibc_mallinfo2 mi = g_mallinfo2();
+    *out = mi;
+    return os::Linux::ok;
+  } else if (g_mallinfo) {
+    // mallinfo() returns 32-bit values. Not perfect but still useful if
+    // process virt size < 4g
+    glibc_mallinfo mi = g_mallinfo();
+    out->arena = (int) mi.arena;
+    out->ordblks = (int) mi.ordblks;
+    out->smblks = (int) mi.smblks;
+    out->hblks = (int) mi.hblks;
+    out->hblkhd = (int) mi.hblkhd;
+    out->usmblks = (int) mi.usmblks;
+    out->fsmblks = (int) mi.fsmblks;
+    out->uordblks = (int) mi.uordblks;
+    out->fordblks = (int) mi.fordblks;
+    out->keepcost = (int) mi.keepcost;
+    return os::Linux::ok_but_possibly_wrapped;
+  }
+  return os::Linux::ok;
+}
+#endif // __GLIBC__
+
+// Trim-native support
+bool os::can_trim_native_heap() {
+#ifdef __GLIBC__
+  return true;
+#else
+  return false; // musl
+#endif
+}
+
+static const size_t retain_size = 2 * M;
+
+bool os::should_trim_native_heap() {
+#ifdef __GLIBC__
+  bool rc = true;
+  // We try, using mallinfo, to predict whether a malloc_trim(3) will be beneficial.
+  //
+  // "mallinfo::keepcost" is no help even if manpage claims this to be the projected
+  // trim size. In practice it is just a very small value with no relation to the actual
+  // effect trimming will have.
+  //
+  // Our best bet is "mallinfo::fordblks", the total chunk size of free blocks. Since
+  // only free blocks can be trimmed, a very low bar is to require their combined size
+  // to be higher than our retain size. Note, however, that "mallinfo::fordblks" includes
+  // already-trimmed blocks, since glibc trims by calling madvice(MADV_DONT_NEED) on free
+  // chunks but does not update its bookkeeping.
+  //
+  // In the end we want to prevent obvious bogus attempts to trim, and for that fordblks
+  // is good enough.
+  os::Linux::glibc_mallinfo2 mi;
+  os::Linux::mallinfo_retval_t mirc = os::Linux::get_mallinfo(&mi);
+  const size_t total_free = mi.fordblks;
+  if (mirc == os::Linux::ok) {
+    rc = retain_size < total_free;
+  }
+  return rc;
+#else
+  return false; // musl
+#endif
+}
+
+bool os::trim_native_heap(os::size_change_t* rss_change) {
+#ifdef __GLIBC__
+  os::Linux::meminfo_t info1;
+  os::Linux::meminfo_t info2;
+
+  bool have_info1 = os::Linux::query_process_memory_info(&info1);
+  ::malloc_trim(retain_size);
+  bool have_info2 = have_info1 && os::Linux::query_process_memory_info(&info2);
+
+  if (have_info1 && have_info2 &&
+    info1.vmrss != -1 && info2.vmrss != -1 &&
+    info1.vmswap != -1 && info2.vmswap != -1) {
+    // Note: query_process_memory_info returns values in K
+    rss_change->before = (info1.vmrss + info1.vmswap) * K;
+    rss_change->after = (info2.vmrss + info2.vmswap) * K;
+  } else {
+    rss_change->after = rss_change->before = SIZE_MAX;
+  }
+
+  return true;
+#else
+  return false; // musl
+#endif
+}
\ No newline at end of file
diff --git a/hotspot/src/os/linux/vm/os_linux.hpp b/hotspot/src/os/linux/vm/os_linux.hpp
index 2bb3fd2..6c27bcb 100644
--- a/hotspot/src/os/linux/vm/os_linux.hpp
+++ b/hotspot/src/os/linux/vm/os_linux.hpp
@@ -243,7 +243,7 @@ class Linux {
   public:
   static pthread_condattr_t* condAttr() { return _condattr; }

-  // Output structure for query_process_memory_info()
+  // Output structure for query_process_memory_info() (all values in KB)
   struct meminfo_t {
     ssize_t vmsize;     // current virtual size
     ssize_t vmpeak;     // peak virtual size
@@ -338,40 +338,6 @@ private:
   };
   static NumaAllocationPolicy _current_numa_policy;

-#ifdef __GLIBC__
-  struct glibc_mallinfo {
-    int arena;
-    int ordblks;
-    int smblks;
-    int hblks;
-    int hblkhd;
-    int usmblks;
-    int fsmblks;
-    int uordblks;
-    int fordblks;
-    int keepcost;
-  };
-
-  struct glibc_mallinfo2 {
-    size_t arena;
-    size_t ordblks;
-    size_t smblks;
-    size_t hblks;
-    size_t hblkhd;
-    size_t usmblks;
-    size_t fsmblks;
-    size_t uordblks;
-    size_t fordblks;
-    size_t keepcost;
-  };
-
-  typedef struct glibc_mallinfo (*mallinfo_func_t)(void);
-  typedef struct glibc_mallinfo2 (*mallinfo2_func_t)(void);
-
-  static mallinfo_func_t _mallinfo;
-  static mallinfo2_func_t _mallinfo2;
-#endif
-
 public:
   static int sched_getcpu()  { return _sched_getcpu != NULL ? _sched_getcpu() : -1; }
   static int numa_node_to_cpus(int node, unsigned long *buffer, int bufferlen) {
@@ -484,6 +450,27 @@ public:
       return false;
     }
   }
+
+#ifdef __GLIBC__
+  struct glibc_mallinfo2 {
+    size_t arena;
+    size_t ordblks;
+    size_t smblks;
+    size_t hblks;
+    size_t hblkhd;
+    size_t usmblks;
+    size_t fsmblks;
+    size_t uordblks;
+    size_t fordblks;
+    size_t keepcost;
+  };
+  enum mallinfo_retval_t { ok, error, ok_but_possibly_wrapped };
+  // get_mallinfo() is a wrapper for mallinfo/mallinfo2. It will prefer mallinfo2() if found.
+  // If we only have mallinfo(), values may be 32-bit truncated, which is signaled via
+  // "ok_but_possibly_wrapped".
+  static mallinfo_retval_t get_mallinfo(glibc_mallinfo2* out);
+#endif
+
 };


diff --git a/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp b/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp
index 95d03d9..39d47a3 100644
--- a/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp
+++ b/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 SAP SE. All rights reserved.
+ * Copyright (c) 2022 SAP SE. All rights reserved.
  * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -25,53 +25,28 @@

 #include "precompiled.hpp"
 #include "runtime/os.hpp"
+#include "trimCHeapDCmd.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/ostream.hpp"
-#include "trimCHeapDCmd.hpp"
+#include "utilities/globalDefinitions.hpp"

 #include <malloc.h>

 void TrimCLibcHeapDCmd::execute(DCmdSource source, TRAPS) {
-#ifdef __GLIBC__
-  stringStream ss_report(1024); // Note: before calling trim
-
-  os::Linux::meminfo_t info1;
-  os::Linux::meminfo_t info2;
-  // Query memory before...
-  bool have_info1 = os::Linux::query_process_memory_info(&info1);
-
-  _output->print_cr("Attempting trim...");
-  ::malloc_trim(0);
-  _output->print_cr("Done.");
-
-  // ...and after trim.
-  bool have_info2 = os::Linux::query_process_memory_info(&info2);
-
-  // Print report both to output stream as well to UL
-  bool wrote_something = false;
-  if (have_info1 && have_info2) {
-    if (info1.vmsize != -1 && info2.vmsize != -1) {
-      ss_report.print_cr("Virtual size before: " SSIZE_FORMAT "k, after: " SSIZE_FORMAT "k, (" SSIZE_FORMAT "k)",
-                         info1.vmsize, info2.vmsize, (info2.vmsize - info1.vmsize));
-      wrote_something = true;
-    }
-    if (info1.vmrss != -1 && info2.vmrss != -1) {
-      ss_report.print_cr("RSS before: " SSIZE_FORMAT "k, after: " SSIZE_FORMAT "k, (" SSIZE_FORMAT "k)",
-                         info1.vmrss, info2.vmrss, (info2.vmrss - info1.vmrss));
-      wrote_something = true;
-    }
-    if (info1.vmswap != -1 && info2.vmswap != -1) {
-      ss_report.print_cr("Swap before: " SSIZE_FORMAT "k, after: " SSIZE_FORMAT "k, (" SSIZE_FORMAT "k)",
-                         info1.vmswap, info2.vmswap, (info2.vmswap - info1.vmswap));
-      wrote_something = true;
+  if (os::can_trim_native_heap()) {
+    os::size_change_t sc;
+    if (os::trim_native_heap(&sc)) {
+      _output->print("Trim native heap: ");
+      if (sc.after != SIZE_MAX) {
+        const size_t delta = sc.after < sc.before ? (sc.before - sc.after) : (sc.after - sc.before);
+        const char sign = sc.after < sc.before ? '-' : '+';
+        _output->print_cr("RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT ")",
+                          PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta));
+      } else {
+        _output->print_cr("(no details available).");
+      }
     }
+  } else {
+    _output->print_cr("Not available.");
   }
-  if (!wrote_something) {
-    ss_report.print_raw("No details available.");
-  }
-
-  _output->print_raw(ss_report.base());
-#else
-  _output->print_cr("Not available.");
-#endif
 }
diff --git a/hotspot/src/os/windows/vm/os_windows.cpp b/hotspot/src/os/windows/vm/os_windows.cpp
index 500a412..25122de 100644
--- a/hotspot/src/os/windows/vm/os_windows.cpp
+++ b/hotspot/src/os/windows/vm/os_windows.cpp
@@ -5957,3 +5957,7 @@ void TestReserveMemorySpecial_test() {
 }
 #endif // PRODUCT

+// stubbed-out trim-native support
+bool os::can_trim_native_heap() { return false; }
+bool os::should_trim_native_heap() { return false; }
+bool os::trim_native_heap(os::size_change_t* rss_change) { return false; }
\ No newline at end of file
diff --git a/hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp b/hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp
index ba156a2..7188925 100644
--- a/hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp
+++ b/hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp
@@ -53,6 +53,7 @@
 #include "gc_implementation/shared/gcTimer.hpp"
 #include "gc_implementation/shared/gcTrace.hpp"
 #include "gc_implementation/shared/gcTraceTime.hpp"
+#include "gc_implementation/shared/gcTrimNativeHeap.hpp"
 #include "gc_implementation/shared/isGCActiveMark.hpp"
 #include "memory/allocation.hpp"
 #include "memory/heapInspection.hpp"
@@ -1304,6 +1305,9 @@ bool G1CollectedHeap::do_collection(bool explicit_gc,
       TraceCollectorStats tcs(g1mm()->full_collection_counters());
       TraceMemoryManagerStats tms(true /* fullGC */, gc_cause());

+      // Pause native trimming for the duration of the GC
+      GCTrimNative::pause_periodic_trim();
+
       double start = os::elapsedTime();
       g1_policy()->record_full_collection_start();

@@ -1546,6 +1550,8 @@ bool G1CollectedHeap::do_collection(bool explicit_gc,

     gc_timer->register_gc_end();
     gc_tracer->report_gc_end(gc_timer->gc_end(), gc_timer->time_partitions());
+
+    GCTrimNative::schedule_trim();
   }
   return true;
 }
diff --git a/hotspot/src/share/vm/gc_implementation/parallelScavenge/parallelScavengeHeap.cpp b/hotspot/src/share/vm/gc_implementation/parallelScavenge/parallelScavengeHeap.cpp
index 74c1584..1c47125 100644
--- a/hotspot/src/share/vm/gc_implementation/parallelScavenge/parallelScavengeHeap.cpp
+++ b/hotspot/src/share/vm/gc_implementation/parallelScavenge/parallelScavengeHeap.cpp
@@ -36,6 +36,7 @@
 #include "gc_implementation/parallelScavenge/psScavenge.hpp"
 #include "gc_implementation/parallelScavenge/vmPSOperations.hpp"
 #include "gc_implementation/shared/gcHeapSummary.hpp"
+#include "gc_implementation/shared/gcTrimNativeHeap.hpp"
 #include "gc_implementation/shared/gcWhen.hpp"
 #include "memory/gcLocker.inline.hpp"
 #include "oops/oop.inline.hpp"
@@ -147,6 +148,8 @@ void ParallelScavengeHeap::post_initialize() {
     PSMarkSweep::initialize();
   }
   PSPromotionManager::initialize();
+
+  GCTrimNative::initialize(true);
 }

 void ParallelScavengeHeap::update_counters() {
diff --git a/hotspot/src/share/vm/gc_implementation/parallelScavenge/psParallelCompact.cpp b/hotspot/src/share/vm/gc_implementation/parallelScavenge/psParallelCompact.cpp
index 3f103ee..26d64a1 100644
--- a/hotspot/src/share/vm/gc_implementation/parallelScavenge/psParallelCompact.cpp
+++ b/hotspot/src/share/vm/gc_implementation/parallelScavenge/psParallelCompact.cpp
@@ -42,6 +42,7 @@
 #include "gc_implementation/shared/gcTimer.hpp"
 #include "gc_implementation/shared/gcTrace.hpp"
 #include "gc_implementation/shared/gcTraceTime.hpp"
+#include "gc_implementation/shared/gcTrimNativeHeap.hpp"
 #include "gc_implementation/shared/isGCActiveMark.hpp"
 #include "gc_interface/gcCause.hpp"
 #include "memory/gcLocker.inline.hpp"
@@ -2008,6 +2009,9 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) {
     return false;
   }

+  // Pause native trimming for the duration of the GC
+  GCTrimNative::pause_periodic_trim();
+
   ParallelScavengeHeap* heap = gc_heap();

   _gc_timer.register_gc_start();
@@ -2182,6 +2186,8 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) {
     // Resize the metaspace capactiy after a collection
     MetaspaceGC::compute_new_size();

+    GCTrimNative::schedule_trim();
+
     if (TraceGen1Time) accumulated_time()->stop();

     if (PrintGC) {
diff --git a/hotspot/src/share/vm/gc_implementation/shared/concurrentGCThread.cpp b/hotspot/src/share/vm/gc_implementation/shared/concurrentGCThread.cpp
index e39fd7a..024499a 100644
--- a/hotspot/src/share/vm/gc_implementation/shared/concurrentGCThread.cpp
+++ b/hotspot/src/share/vm/gc_implementation/shared/concurrentGCThread.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -40,12 +40,12 @@ ConcurrentGCThread::ConcurrentGCThread() :
   _should_terminate(false), _has_terminated(false) {
 };

-void ConcurrentGCThread::create_and_start() {
+void ConcurrentGCThread::create_and_start(ThreadPriority prio) {
   if (os::create_thread(this, os::cgc_thread)) {
     // XXX: need to set this to low priority
     // unless "agressive mode" set; priority
     // should be just less than that of VMThread.
-    os::set_priority(this, NearMaxPriority);
+    os::set_priority(this, prio);
     if (!_should_terminate && !DisableStartThread) {
       os::start_thread(this);
     }
@@ -63,7 +63,7 @@ void ConcurrentGCThread::initialize_in_thread() {
 void ConcurrentGCThread::wait_for_universe_init() {
   MutexLockerEx x(CGC_lock, Mutex::_no_safepoint_check_flag);
   while (!is_init_completed() && !_should_terminate) {
-    CGC_lock->wait(Mutex::_no_safepoint_check_flag, 200);
+    CGC_lock->wait(Mutex::_no_safepoint_check_flag, 1);
   }
 }

diff --git a/hotspot/src/share/vm/gc_implementation/shared/concurrentGCThread.hpp b/hotspot/src/share/vm/gc_implementation/shared/concurrentGCThread.hpp
index e87228b..1e16bf7 100644
--- a/hotspot/src/share/vm/gc_implementation/shared/concurrentGCThread.hpp
+++ b/hotspot/src/share/vm/gc_implementation/shared/concurrentGCThread.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -33,7 +33,7 @@ class ConcurrentGCThread: public NamedThread {
   friend class VMStructs;

 protected:
-  bool _should_terminate;
+  bool volatile _should_terminate;
   bool _has_terminated;

   enum CGC_flag_type {
@@ -50,7 +50,7 @@ protected:
   static int reset_CGC_flag(int b)         { return _CGC_flag &= ~b; }

   // Create and start the thread (setting it's priority high.)
-  void create_and_start();
+  void create_and_start(ThreadPriority prio = NearMaxPriority);

   // Do initialization steps in the thread: record stack base and size,
   // init thread local storage, set JNI handle block.
diff --git a/hotspot/src/share/vm/gc_implementation/shared/gcTrimNativeHeap.cpp b/hotspot/src/share/vm/gc_implementation/shared/gcTrimNativeHeap.cpp
new file mode 100644
index 0000000..b9bac56
--- /dev/null
+++ b/hotspot/src/share/vm/gc_implementation/shared/gcTrimNativeHeap.cpp
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2022 SAP SE. All rights reserved.
+ * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, Huawei Technologies Co., Ltd. All rights reserved.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "gc_implementation/shared/concurrentGCThread.hpp"
+#include "gc_implementation/shared/gcTrimNativeHeap.hpp"
+#include "gc_implementation/g1/g1_globals.hpp"
+#include "runtime/globals.hpp"
+#include "runtime/globals_extension.hpp"
+#include "runtime/mutex.hpp"
+#include "runtime/mutexLocker.hpp"
+#include "runtime/os.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/ostream.hpp"
+#include "utilities/ticks.hpp"
+
+bool GCTrimNative::_async_mode = false;
+double GCTrimNative::_next_trim_not_before = 0;
+
+// GCTrimNative works in two modes:
+//
+// - async mode, where GCTrimNative runs a trimmer thread on behalf of the GC.
+//   The trimmer thread will be doing all the trims, both periodically and
+//   triggered from outside via GCTrimNative::schedule_trim().
+//
+// - synchronous mode, where the GC does the trimming itself in its own thread,
+//   via GCTrimNative::should_trim() and GCTrimNative::execute_trim().
+//
+// The mode is set as argument to GCTrimNative::initialize().
+
+class NativeTrimmer : public ConcurrentGCThread {
+
+  Monitor* _lock;
+  volatile jlong _paused;
+  static NativeTrimmer* _the_trimmer;
+
+public:
+
+  virtual void run() {
+    initialize_in_thread();
+    wait_for_universe_init();
+
+    assert(GCTrimNativeHeap, "Sanity");
+    assert(os::can_trim_native_heap(), "Sanity");
+
+    gclog_or_tty->print_cr("NativeTrimmer started.");
+
+    // Note: GCTrimNativeHeapInterval=0 -> zero wait time -> indefinite waits, disabling periodic trim
+    const int64_t delay_ms = GCTrimNativeHeapInterval * 1000;
+    for (;;) {
+      MonitorLockerEx ml(_lock, Mutex::_no_safepoint_check_flag);
+      ml.wait(Mutex::_no_safepoint_check_flag, delay_ms);
+      if (_should_terminate) {
+        gclog_or_tty->print_cr("NativeTrimmer stopped.");
+        break;
+      }
+      jlong paused = Atomic::load(&_paused);
+      if (!paused && os::should_trim_native_heap()) {
+        GCTrimNative::do_trim();
+      }
+    }
+
+    terminate();
+  }
+
+  void stop() {
+    {
+      MutexLockerEx ml(Terminator_lock);
+      _should_terminate = true;
+    }
+
+    wakeup();
+
+    {
+      MutexLockerEx ml(Terminator_lock);
+      while (!_has_terminated) {
+        Terminator_lock->wait();
+      }
+    }
+  }
+
+protected:
+
+  void wakeup() {
+    MonitorLockerEx ml(_lock, Mutex::_no_safepoint_check_flag);
+    ml.notify_all();
+  }
+
+  void pause() {
+    Atomic::store(1, &_paused);
+    debug_only(gclog_or_tty->print_cr("NativeTrimmer paused"));
+  }
+
+  void unpause() {
+    Atomic::store(0, &_paused);
+    debug_only(gclog_or_tty->print_cr("NativeTrimmer unpaused"));
+  }
+
+public:
+
+  NativeTrimmer() :
+    _paused(0)
+  {
+    //Mutex::leaf+8 just for NativeTrimmer_lock
+    _lock = new (std::nothrow) Monitor(Mutex::leaf+8, "NativeTrimmer_lock", true);
+    set_name("NativeTrimmer Thread");
+  }
+
+  static bool is_enabled() {
+    return _the_trimmer != NULL;
+  }
+
+  static void start_trimmer() {
+    _the_trimmer = new NativeTrimmer();
+    _the_trimmer->create_and_start(NormPriority);
+  }
+
+  static void stop_trimmer() {
+    _the_trimmer->stop();
+  }
+
+  static void pause_periodic_trim() {
+    _the_trimmer->pause();
+  }
+
+  static void unpause_periodic_trim() {
+    _the_trimmer->unpause();
+  }
+
+  static void schedule_trim_now() {
+    _the_trimmer->unpause();
+    _the_trimmer->wakeup();
+  }
+
+}; // NativeTrimmer
+
+NativeTrimmer* NativeTrimmer::_the_trimmer = NULL;
+
+void GCTrimNative::do_trim() {
+  Ticks start = Ticks::now();
+  os::size_change_t sc;
+  if (os::trim_native_heap(&sc)) {
+    Tickspan trim_time = (Ticks::now() - start);
+    if (sc.after != SIZE_MAX) {
+      const size_t delta = sc.after < sc.before ? (sc.before - sc.after) : (sc.after - sc.before);
+      const char sign = sc.after < sc.before ? '-' : '+';
+      gclog_or_tty->print_cr("Trim native heap: RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT "), %1.3fms",
+                         PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta),
+                         trim_time.seconds() * 1000);
+    } else {
+      gclog_or_tty->print_cr("Trim native heap (no details)");
+    }
+  }
+}
+
+/// GCTrimNative outside facing methods
+
+void GCTrimNative::initialize(bool async_mode) {
+
+  if (GCTrimNativeHeap) {
+
+    if (!os::can_trim_native_heap()) {
+      FLAG_SET_ERGO(bool, GCTrimNativeHeap, false);
+      gclog_or_tty->print_cr("GCTrimNativeHeap disabled - trim-native not supported on this platform.");
+      return;
+    }
+
+    debug_only(gclog_or_tty->print_cr("GCTrimNativeHeap enabled."));
+
+    _async_mode = async_mode;
+
+    // If we are to run the trimmer on behalf of the GC:
+    if (_async_mode) {
+      NativeTrimmer::start_trimmer();
+    }
+
+    _next_trim_not_before = GCTrimNativeHeapInterval;
+  }
+}
+
+void GCTrimNative::cleanup() {
+  if (GCTrimNativeHeap) {
+    if (_async_mode) {
+      NativeTrimmer::stop_trimmer();
+    }
+  }
+}
+
+bool GCTrimNative::should_trim(bool ignore_delay) {
+  return
+      GCTrimNativeHeap && os::can_trim_native_heap() &&
+      (ignore_delay || (GCTrimNativeHeapInterval > 0 && os::elapsedTime() > _next_trim_not_before)) &&
+      os::should_trim_native_heap();
+}
+
+void GCTrimNative::execute_trim() {
+  if (GCTrimNativeHeap) {
+    assert(!_async_mode, "Only call for non-async mode");
+    do_trim();
+    _next_trim_not_before = os::elapsedTime() + GCTrimNativeHeapInterval;
+  }
+}
+
+void GCTrimNative::pause_periodic_trim() {
+  if (GCTrimNativeHeap) {
+    assert(_async_mode, "Only call for async mode");
+    NativeTrimmer::pause_periodic_trim();
+  }
+}
+
+void GCTrimNative::unpause_periodic_trim() {
+  if (GCTrimNativeHeap) {
+    assert(_async_mode, "Only call for async mode");
+    NativeTrimmer::unpause_periodic_trim();
+  }
+}
+
+void GCTrimNative::schedule_trim() {
+  if (GCTrimNativeHeap) {
+    assert(_async_mode, "Only call for async mode");
+    NativeTrimmer::schedule_trim_now();
+  }
+}
diff --git a/hotspot/src/share/vm/gc_implementation/shared/gcTrimNativeHeap.hpp b/hotspot/src/share/vm/gc_implementation/shared/gcTrimNativeHeap.hpp
new file mode 100644
index 0000000..f586093
--- /dev/null
+++ b/hotspot/src/share/vm/gc_implementation/shared/gcTrimNativeHeap.hpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022 SAP SE. All rights reserved.
+ * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, Huawei Technologies Co., Ltd. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP
+#define SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP
+
+#include "memory/allocation.hpp"
+
+class NativeTrimmer;
+
+class GCTrimNative : public AllStatic {
+  friend class NativeTrimmer;
+
+  static bool _async_mode;
+  static double _next_trim_not_before;
+
+  static void do_trim();
+
+public:
+
+  static void initialize(bool async_mode);
+  static void cleanup();
+
+  // Returns true if:
+  // - trimming is enabled and possible
+  // - trimming may have an actual effect (guess)
+  // - delay timer has expired (unless ignore_delay is true)
+  static bool should_trim(bool ignore_delay);
+
+  // Execute trim-native in this thread
+  static void execute_trim();
+
+  // Pause/unpause periodic trim
+  static void pause_periodic_trim();
+  static void unpause_periodic_trim();
+
+  // Schedule an explicit trim now; if periodic trims had been
+  // paused, they are unpaused.
+  static void schedule_trim();
+
+};
+
+#endif // SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP
diff --git a/hotspot/src/share/vm/memory/genCollectedHeap.cpp b/hotspot/src/share/vm/memory/genCollectedHeap.cpp
index 20fbbfd..7df3d68 100644
--- a/hotspot/src/share/vm/memory/genCollectedHeap.cpp
+++ b/hotspot/src/share/vm/memory/genCollectedHeap.cpp
@@ -58,6 +58,7 @@
 #if INCLUDE_ALL_GCS
 #include "gc_implementation/concurrentMarkSweep/concurrentMarkSweepThread.hpp"
 #include "gc_implementation/concurrentMarkSweep/vmCMSOperations.hpp"
+#include "gc_implementation/shared/gcTrimNativeHeap.hpp"
 #endif // INCLUDE_ALL_GCS
 #if INCLUDE_JFR
 #include "jfr/jfr.hpp"
@@ -572,6 +573,11 @@ void GenCollectedHeap::do_collection(bool  full,
       update_full_collections_completed();
     }

+    // Trim the native heap, without a delay since this is a full gc
+    if (full && GCTrimNative::should_trim(true)) {
+      GCTrimNative::execute_trim();
+    }
+
     // Track memory usage and detect low memory after GC finishes
     MemoryService::track_memory_usage();

diff --git a/hotspot/src/share/vm/memory/sharedHeap.cpp b/hotspot/src/share/vm/memory/sharedHeap.cpp
index ef22f01..8c02320 100644
--- a/hotspot/src/share/vm/memory/sharedHeap.cpp
+++ b/hotspot/src/share/vm/memory/sharedHeap.cpp
@@ -26,6 +26,7 @@
 #include "classfile/symbolTable.hpp"
 #include "classfile/systemDictionary.hpp"
 #include "code/codeCache.hpp"
+#include "gc_implementation/shared/gcTrimNativeHeap.hpp"
 #include "gc_interface/collectedHeap.inline.hpp"
 #include "memory/sharedHeap.hpp"
 #include "oops/oop.inline.hpp"
@@ -104,6 +105,11 @@ void SharedHeap::set_barrier_set(BarrierSet* bs) {
 void SharedHeap::post_initialize() {
   CollectedHeap::post_initialize();
   ref_processing_init();
+  if (!UseSerialGC) {
+      GCTrimNative::initialize(true);
+  } else {
+      GCTrimNative::initialize(false); // false since we will call trim inside the collecting thread
+  }
 }

 void SharedHeap::ref_processing_init() {}
diff --git a/hotspot/src/share/vm/runtime/globals.hpp b/hotspot/src/share/vm/runtime/globals.hpp
index 0dab18e..ec48c48 100644
--- a/hotspot/src/share/vm/runtime/globals.hpp
+++ b/hotspot/src/share/vm/runtime/globals.hpp
@@ -3387,6 +3387,16 @@ class CommandLineFlags {
           "Number of entries we will try to leave on the stack "            \
           "during parallel gc")                                             \
                                                                             \
+  experimental(bool, GCTrimNativeHeap, false,                               \
+          "GC will attempt to trim the native heap periodically and at "    \
+          "full GCs.")                                                      \
+                                                                            \
+  experimental(uintx, GCTrimNativeHeapInterval, 60,                          \
+          "If GCTrimNativeHeap is enabled: interval time, in seconds, in "  \
+          "which the VM will attempt to trim the native heap. A value of "  \
+          "0 disables periodic trimming while leaving trimming at full gc " \
+          "enabled.")                                                       \
+                                                                            \
   /* stack parameters */                                                    \
   product_pd(intx, StackYellowPages,                                        \
           "Number of yellow zone (recoverable overflows) pages")            \
diff --git a/hotspot/src/share/vm/runtime/init.cpp b/hotspot/src/share/vm/runtime/init.cpp
index d15e40d..d2e0f22 100644
--- a/hotspot/src/share/vm/runtime/init.cpp
+++ b/hotspot/src/share/vm/runtime/init.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -166,8 +166,7 @@ void exit_globals() {
   }
 }

-
-static bool _init_completed = false;
+static volatile bool _init_completed = false;

 bool is_init_completed() {
   return _init_completed;
diff --git a/hotspot/src/share/vm/runtime/java.cpp b/hotspot/src/share/vm/runtime/java.cpp
index 4f290c8..54b980d 100644
--- a/hotspot/src/share/vm/runtime/java.cpp
+++ b/hotspot/src/share/vm/runtime/java.cpp
@@ -30,6 +30,7 @@
 #include "code/codeCache.hpp"
 #include "compiler/compileBroker.hpp"
 #include "compiler/compilerOracle.hpp"
+#include "gc_implementation/shared/gcTrimNativeHeap.hpp"
 #include "interpreter/bytecodeHistogram.hpp"
 #include "jfr/jfrEvents.hpp"
 #include "jfr/support/jfrThreadId.hpp"
@@ -509,6 +510,8 @@ void before_exit(JavaThread * thread) {
   StatSampler::disengage();
   StatSampler::destroy();

+  GCTrimNative::cleanup();
+
   // Stop concurrent GC threads
   Universe::heap()->stop();

diff --git a/hotspot/src/share/vm/runtime/os.hpp b/hotspot/src/share/vm/runtime/os.hpp
index 7ae49fd..acc57f4 100644
--- a/hotspot/src/share/vm/runtime/os.hpp
+++ b/hotspot/src/share/vm/runtime/os.hpp
@@ -333,6 +333,17 @@ class os: AllStatic {
   static bool   uncommit_memory(char* addr, size_t bytes);
   static bool   release_memory(char* addr, size_t bytes);

+  // Does the platform support trimming the native heap?
+  static bool can_trim_native_heap();
+
+  // Does the platform recommend trimming?
+  static bool should_trim_native_heap();
+
+  // Trim the C-heap. Returns RSS size change and optionally return the rss size change.
+  // If trim was done but size change could not be obtained, SIZE_MAX is returned for after size.
+  struct size_change_t { size_t before; size_t after; };
+  static bool trim_native_heap(size_change_t* rss_change);
+
   // Touch memory pages that cover the memory range from start to end (exclusive)
   // to make the OS back the memory range with actual memory.
   // Current implementation may not touch the last page if unaligned addresses
diff --git a/hotspot/src/share/vm/utilities/globalDefinitions.hpp b/hotspot/src/share/vm/utilities/globalDefinitions.hpp
index 25f6f02..12eea20 100644
--- a/hotspot/src/share/vm/utilities/globalDefinitions.hpp
+++ b/hotspot/src/share/vm/utilities/globalDefinitions.hpp
@@ -260,6 +260,9 @@ inline T byte_size_in_proper_unit(T s) {
   }
 }

+#define PROPERFMT             SIZE_FORMAT "%s"
+#define PROPERFMTARGS(S)      byte_size_in_proper_unit(S), proper_unit_for_byte_size(S)
+
 //----------------------------------------------------------------------------------------------------
 // VM type definitions

diff --git a/hotspot/test/gc/TestTrimNative.java b/hotspot/test/gc/TestTrimNative.java
new file mode 100644
index 0000000..58d5405
--- /dev/null
+++ b/hotspot/test/gc/TestTrimNative.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) 2022 SAP SE. All rights reserved.
+ * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, Huawei Technologies Co., Ltd. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+package gc;
+
+/*
+ * All these tests test the trim-native feature for all GCs.
+ * Trim-native is the ability to trim the C-heap as part of the GC cycle.
+ * This feature is controlled by -XX:+GCTrimNativeHeap (by default off).
+ * Trimming happens on full gc for all gcs. Shenandoah and G1 also support
+ * concurrent trimming (Shenandoah supports this without any ties to java
+ * heap occupancy).
+ *
+ */
+
+//// full gc tests /////
+
+/*
+ * @test id=fullgc-serial
+ * @summary Test that GCTrimNativeHeap works with Serial
+ * @requires vm.gc=="Serial"
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-fullgc serial
+ */
+
+/*
+ * @test id=fullgc-parallel
+ * @summary Test that GCTrimNativeHeap works with Parallel
+ * @requires vm.gc=="Parallel"
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-fullgc parallel
+ */
+
+/*
+ * @test id=fullgc-g1
+ * @summary Test that GCTrimNativeHeap works with G1
+ * @requires vm.gc=="G1"
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-fullgc g1
+ */
+
+//// auto mode tests /////
+
+// Note: not serial, since it does not do periodic trimming, only trimming on full gc
+
+/*
+ * @test id=auto-parallel
+ * @summary Test that GCTrimNativeHeap works with Parallel
+ * @requires vm.gc=="Parallel"
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-auto parallel
+ */
+
+/*
+ * @test id=auto-g1
+ * @summary Test that GCTrimNativeHeap works with G1
+ * @requires vm.gc=="G1"
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-auto g1
+ */
+
+
+//// test-auto-high-interval interval test /////
+
+// Note: not serial, since it does not do periodic trimming, only trimming on full gc
+
+/*
+ * @test id=auto-high-interval-parallel
+ * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming
+ * @requires vm.gc=="Parallel"
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-auto-high-interval parallel
+ */
+
+/*
+ * @test id=auto-high-interval-g1
+ * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming
+ * @requires vm.gc=="G1"
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-auto-high-interval g1
+ */
+
+//// test-auto-interval-0 test /////
+
+// Note: not serial, since it does not do periodic trimming, only trimming on full gc
+
+/*
+ * @test id=auto-zero-interval-parallel
+ * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming
+ * @requires vm.gc=="Parallel"
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-auto-zero-interval parallel
+ */
+
+/*
+ * @test id=auto-zero-interval-g1
+ * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming
+ * @requires vm.gc=="G1"
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-auto-zero-interval g1
+ */
+
+// Other tests
+
+/*
+ * @test id=off-explicit
+ * @summary Test that -GCTrimNative disables the feature
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-off-explicit
+ */
+
+/*
+ * @test id=off-by-default
+ * @summary Test that GCTrimNative is off by default
+ * @requires os.family=="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-off-by-default
+ */
+
+/*
+ * @test id=off-on-other-platforms
+ * @summary Test that GCTrimNative is off on unsupportive platforms
+ * @requires os.family!="linux"
+ * @modules java.base/jdk.internal.misc
+ * @library /testlibrary
+ * @run driver gc.TestTrimNative test-off-on-other-platforms
+ */
+
+import sun.misc.Unsafe;
+import com.oracle.java.testlibrary.*;
+
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class TestTrimNative {
+
+    // Actual RSS increase is a lot larger than 4 MB. Depends on glibc overhead, and NMT malloc headers in debug VMs.
+    // We need small-grained allocations to make sure they actually increase RSS (all touched) and to see the
+    // glibc-retaining-memory effect.
+    static final int szAllocations = 16;
+    static final int totalAllocationsSize = 16 * 1024 * 1024; // 16 MB total
+    static final int numAllocations = totalAllocationsSize / szAllocations;
+
+    static long[] ptrs = new long[numAllocations];
+
+    enum Unit {
+        B(1), K(1024), M(1024*1024), G(1024*1024*1024);
+        public final long size;
+        Unit(long size) { this.size = size; }
+    }
+
+    enum GC {
+        serial, parallel, g1, shenandoah, z;
+        String getSwitchName() {
+            String s = name();
+            return "-XX:+Use" + s.substring(0, 1).toUpperCase() + s.substring(1) + "GC";
+        }
+        boolean isZ() { return this == GC.z; }
+        boolean isSerial() { return this == GC.serial; }
+        boolean isParallel() { return this == GC.parallel; }
+        boolean isG1() { return this == GC.g1; }
+        boolean isShenandoah() { return this == GC.shenandoah; }
+    }
+
+    static private boolean usesNativeTrimmer(GC gc) {
+        return gc.isG1() || gc.isParallel() || gc.isZ();
+    }
+
+    static private final OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] testArgs) throws Exception {
+
+        List<String> allOptions = new ArrayList<String>();
+        allOptions.add("-XX:+UnlockExperimentalVMOptions");
+        allOptions.addAll(Arrays.asList(extraOptions));
+        allOptions.add("-Xmx128m");
+        allOptions.add("-Xms128m"); // Stabilize RSS
+        allOptions.add("-XX:+AlwaysPreTouch"); // Stabilize RSS
+
+        allOptions.add(TestTrimNative.class.getName());
+        allOptions.add("RUN");
+        allOptions.addAll(Arrays.asList(testArgs));
+        ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(allOptions.toArray(new String[0]));
+        OutputAnalyzer output = new OutputAnalyzer(pb.start());
+        output.shouldHaveExitValue(0);
+        return output;
+
+    }
+
+    /**
+     * Given JVM output, look for a log line that describes a successful negative trim in the megabyte range
+     * like this:
+     * "[2.053s][debug][gc,trim] Trim native heap (retain size: 5120K): RSS+Swap: 271M->223M (-49112K), 2.834ms"
+     * (Note: we use the "properXXX" print routines, therefore units can differ)
+     * Check that the sum of all trim log lines comes to a total RSS reduction in the MB range
+     * @param output
+     * @param minExpected min number of trim lines expected in UL log
+     * @param maxExpected max number of trim lines expected in UL log
+     */
+    private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int minExpected, int maxExpected) {
+        output.reportDiagnosticSummary();
+        List<String> lines = output.asLines();
+        Pattern pat = Pattern.compile(".*\\[gc,trim\\] Trim native heap.*RSS\\+Swap: (\\d+)([KMB])->(\\d+)([KMB]).*");
+        int numTrimsFound = 0;
+        long rssReductionTotal = 0;
+        for (String line : lines) {
+            Matcher mat = pat.matcher(line);
+            if (mat.matches()) {
+                long rss1 = Long.parseLong(mat.group(1)) * Unit.valueOf(mat.group(2)).size;
+                long rss2 = Long.parseLong(mat.group(3)) * Unit.valueOf(mat.group(4)).size;
+                System.out.println("Parsed Trim Line. rss1: " + rss1 + " rss2: " + rss2);
+                if (rss1 > rss2) {
+                    rssReductionTotal += (rss1 - rss2);
+                }
+                numTrimsFound ++;
+            }
+            if (numTrimsFound > maxExpected) {
+                throw new RuntimeException("Abnormal high number of trim attempts found (more than " + maxExpected +
+                                            "). Does the interval setting not work?");
+            }
+        }
+        if (numTrimsFound < minExpected) {
+            throw new RuntimeException("We found fewer trim lines in UL log than expected (expected " + minExpected +
+                    ", found " + numTrimsFound + ".");
+        }
+        // This is very fuzzy. We malloced X, free'd X, trimmed, measured the combined effect of all reductions.
+        // This does not take into effect mallocs or frees that may happen concurrently. But we expect to see *some*
+        // reduction somewhere. Test with a fudge factor.
+        float fudge = 0.8f;
+        long expectedMinimalReduction = (long) (totalAllocationsSize * fudge);
+        if (rssReductionTotal < expectedMinimalReduction) {
+            throw new RuntimeException("We did not see the expected RSS reduction in the UL log. Expected (with fudge)" +
+                                       " to see at least a combined reduction of " + expectedMinimalReduction + ".");
+        }
+    }
+
+    // Test that GCTrimNativeHeap=1 causes a trim-native on full gc
+    static private final void testWithFullGC(GC gc) throws Exception {
+        System.out.println("testWithFullGC");
+        int sleeptime_secs = 2;
+        OutputAnalyzer output = runTestWithOptions (
+                new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap" },
+                new String[] { "true" /* full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ }
+        );
+        // With default interval time of 30 seconds, auto trimming should never kick in, so the only
+        // log line we expect to see is the one from the full-gc induced trim.
+        parseOutputAndLookForNegativeTrim(output, 1, 1);
+        // For GCs that use the NativeTrimmer, we want to see the NativeTrimmer paused during the GC, as well as
+        // started and shut down properly.
+        if (usesNativeTrimmer(gc)) {
+            output.shouldContain("NativeTrimmer started");
+            output.shouldContain("NativeTrimmer paused");
+            output.shouldContain("NativeTrimmer unpaused");
+            output.shouldContain("NativeTrimmer stopped");
+        } else {
+            output.shouldNotContain("NativeTrimmer");
+        }
+    }
+
+    // Test that GCTrimNativeHeap=1 causes a trim-native automatically, without GC (for now, shenandoah only)
+    static private final void testAuto(GC gc) throws Exception {
+        System.out.println("testAuto");
+        long t1 = System.currentTimeMillis();
+        int sleeptime_secs = 4;
+        OutputAnalyzer output = runTestWithOptions (
+                new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=1" },
+                new String[] { "false" /* full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ }
+        );
+        long t2 = System.currentTimeMillis();
+        int runtime_s = (int)((t2 - t1) / 1000);
+        // With an interval time of 1 second and a runtime of 6..x seconds we expect to see x log lines (+- fudge factor).
+        parseOutputAndLookForNegativeTrim(output, runtime_s - 4, runtime_s + 2);
+    }
+
+    // Test that trim-native correctly honors interval
+    static private final void testAutoWithHighInterval(GC gc) throws Exception {
+        // We pass a very high interval. This should disable the feature for this short-lived test, we should see no trim
+        System.out.println("testAutoWithHighInterval");
+        OutputAnalyzer output = runTestWithOptions (
+                new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=30" },
+                new String[] { "false" /* full gc */, "6000" /* ms after peak */ }
+        );
+        output.shouldNotContain("Trim native heap");
+    }
+
+    // Test that trim-native correctly honors interval
+    static private final void testAutoWithZeroInterval(GC gc) throws Exception {
+        // We pass a very high interval. This should disable the feature for this short-lived test, we should see no trim
+        System.out.println("testAutoWithHighInterval");
+        OutputAnalyzer output = runTestWithOptions (
+                new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=0" },
+                new String[] { "false" /* full gc */, "6000" /* ms after peak */ }
+        );
+        output.shouldNotContain("Trim native heap");
+    }
+
+    // Test that trim-native gets disabled on platforms that don't support it.
+    static private final void testOffOnNonCompliantPlatforms() throws Exception {
+        // Logic is shared, so no need to test with every GC. Just use the default GC.
+        System.out.println("testOffOnNonCompliantPlatforms");
+        OutputAnalyzer output = runTestWithOptions (
+                new String[] { "-XX:+GCTrimNativeHeap" },
+                new String[] { "true" /* full gc */, "2000" /* ms after peak */ }
+        );
+        output.shouldContain("GCTrimNativeHeap disabled");
+        output.shouldNotContain("Trim native heap");
+    }
+
+    // Test that GCTrimNativeHeap=0 switches trim-native off
+    static private final void testOffExplicit() throws Exception {
+        // Logic is shared, so no need to test with every GC. Just use the default GC.
+        System.out.println("testOffExplicit");
+        OutputAnalyzer output = runTestWithOptions (
+                new String[] { "-XX:-GCTrimNativeHeap" },
+                new String[] { "true" /* full gc */, "2000" /* ms after peak */ }
+        );
+        output.shouldNotContain("Trim native heap");
+    }
+
+    // Test that trim-native is disabled by default
+    static private final void testOffByDefault() throws Exception {
+        // Logic is shared, so no need to test with every GC. Just use the default GC.
+        System.out.println("testOffByDefault");
+        OutputAnalyzer output = runTestWithOptions (
+                new String[] { },
+                new String[] { "true" /* full gc */, "2000" /* ms after peak */ }
+        );
+        output.shouldNotContain("Trim native heap");
+    }
+
+    public static void main(String[] args) throws Exception {
+
+        if (args.length == 0) {
+            throw new RuntimeException("Argument error");
+        }
+
+        if (args[0].equals("RUN")) {
+            boolean doFullGC = Boolean.parseBoolean(args[1]);
+
+            System.out.println("Will spike now...");
+            Field field = Unsafe.class.getDeclaredField("theUnsafe");
+            field.setAccessible(true);
+            Unsafe unsafe = (Unsafe) field.get(null);
+            for (int i = 0; i < numAllocations; i++) {
+                ptrs[i] = unsafe.allocateMemory(szAllocations);
+                unsafe.putByte(ptrs[i], (byte)0);
+                unsafe.putByte(ptrs[i] + szAllocations / 2, (byte)0);
+            }
+            for (int i = 0; i < numAllocations; i++) {
+                unsafe.freeMemory(ptrs[i]);
+            }
+            System.out.println("Done spiking.");
+
+            if (doFullGC) {
+                System.out.println("GC...");
+                System.gc();
+            }
+
+            // give GC time to react
+            int time = Integer.parseInt(args[2]);
+            System.out.println("Sleeping...");
+            Thread.sleep(time);
+            System.out.println("Done.");
+
+            return;
+
+        } else if (args[0].equals("test-fullgc")) {
+            final GC gc = GC.valueOf(args[1]);
+            testWithFullGC(gc);
+        } else if (args[0].equals("test-auto")) {
+            final GC gc = GC.valueOf(args[1]);
+            testAuto(gc);
+        } else if (args[0].equals("test-auto-high-interval")) {
+            final GC gc = GC.valueOf(args[1]);
+            testAutoWithHighInterval(gc);
+        } else if (args[0].equals("test-auto-zero-interval")) {
+            final GC gc = GC.valueOf(args[1]);
+            testAutoWithZeroInterval(gc);
+        } else if (args[0].equals("test-off-explicit")) {
+            testOffExplicit();
+        } else if (args[0].equals("test-off-by-default")) {
+            testOffByDefault();
+        } else if (args[0].equals("test-off-on-other-platforms")) {
+            testOffOnNonCompliantPlatforms();
+        } else {
+            throw new RuntimeException("Invalid test " + args[0]);
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java b/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java
index 0fe8e35..131fa4c 100644
--- a/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java
+++ b/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java
@@ -29,7 +29,7 @@ import com.oracle.java.testlibrary.*;
  * @test
  * @summary Test of diagnostic command VM.trim_libc_heap
  * @library /testlibrary
- * @requires os.family == "linux"
+ * @requires os.family=="linux"
  * @modules java.base/jdk.internal.misc
  *          java.compiler
  *          java.management
@@ -40,10 +40,7 @@ public class TrimLibcHeapTest {
     public void run(CommandExecutor executor) {
         OutputAnalyzer output = executor.execute("System.trim_native_heap");
         output.reportDiagnosticSummary();
-        output.shouldMatch("(Done|Not available)"); // Not available could happen on Linux + non-glibc (eg. muslc)
-        if (output.firstMatch("Done") != null) {
-            output.shouldMatch("(Virtual size before|RSS before|Swap before|No details available)");
-        }
+        output.shouldMatch(".*Trim native heap: RSS\\+Swap: \\d+[BKM]->\\d+[BKM].*");
     }

     @Test
--
1.8.3.1
