From 1b97a08d822b7e2388ded07c696fffe70b39697a Mon Sep 17 00:00:00 2001
From: eapen <zhangyipeng7@huawei.com>
Date: Mon, 12 Dec 2022 17:40:09 +0800
Subject: [PATCH 12/33] I68TO2: 8268893: jcmd to trim the glibc heap
---
 hotspot/src/os/linux/vm/os_linux.cpp               |  57 ++++---
 hotspot/src/os/linux/vm/os_linux.hpp               |  17 ++
 hotspot/src/os/linux/vm/trimCHeapDCmd.cpp          |  77 +++++++++
 hotspot/src/os/linux/vm/trimCHeapDCmd.hpp          |  52 ++++++
 .../src/share/vm/services/diagnosticCommand.cpp    |   7 +
 .../test/serviceability/dcmd/TrimLibcHeapTest.java |  53 ++++++
 .../oracle/java/testlibrary/CommandExecutor.java   |  73 ++++++++
 .../java/testlibrary/CommandExecutorException.java |  36 ++++
 .../com/oracle/java/testlibrary/JMXExecutor.java   | 185 +++++++++++++++++++++
 9 files changed, 532 insertions(+), 25 deletions(-)
 create mode 100644 hotspot/src/os/linux/vm/trimCHeapDCmd.cpp
 create mode 100644 hotspot/src/os/linux/vm/trimCHeapDCmd.hpp
 create mode 100644 hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java
 create mode 100644 hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutor.java
 create mode 100644 hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutorException.java
 create mode 100644 hotspot/test/testlibrary/com/oracle/java/testlibrary/JMXExecutor.java

diff --git a/hotspot/src/os/linux/vm/os_linux.cpp b/hotspot/src/os/linux/vm/os_linux.cpp
index 1a3504f..c687b1c 100644
--- a/hotspot/src/os/linux/vm/os_linux.cpp
+++ b/hotspot/src/os/linux/vm/os_linux.cpp
@@ -2290,44 +2290,51 @@ void os::Linux::print_system_memory_info(outputStream* st) {
    st->cr();
 }

-void os::Linux::print_process_memory_info(outputStream* st) {
-
-  st->print_cr("Process Memory:");
-
-  // Print virtual and resident set size; peak values; swap; and for
-  //  rss its components if the kernel is recent enough.
-  ssize_t vmsize = -1, vmpeak = -1, vmswap = -1,
-      vmrss = -1, vmhwm = -1, rssanon = -1, rssfile = -1, rssshmem = -1;
-  const int num_values = 8;
-  int num_found = 0;
+bool os::Linux::query_process_memory_info(os::Linux::meminfo_t* info) {
   FILE* f = ::fopen("/proc/self/status", "r");
+  const int num_values = sizeof(os::Linux::meminfo_t) / sizeof(size_t);
+  int num_found = 0;
   char buf[256];
+  info->vmsize = info->vmpeak = info->vmrss = info->vmhwm = info->vmswap =
+      info->rssanon = info->rssfile = info->rssshmem = -1;
   if (f != NULL) {
     while (::fgets(buf, sizeof(buf), f) != NULL && num_found < num_values) {
-      if ( (vmsize == -1    && sscanf(buf, "VmSize: " SSIZE_FORMAT " kB", &vmsize) == 1) ||
-           (vmpeak == -1    && sscanf(buf, "VmPeak: " SSIZE_FORMAT " kB", &vmpeak) == 1) ||
-           (vmswap == -1    && sscanf(buf, "VmSwap: " SSIZE_FORMAT " kB", &vmswap) == 1) ||
-           (vmhwm == -1     && sscanf(buf, "VmHWM: " SSIZE_FORMAT " kB", &vmhwm) == 1) ||
-           (vmrss == -1     && sscanf(buf, "VmRSS: " SSIZE_FORMAT " kB", &vmrss) == 1) ||
-           (rssanon == -1   && sscanf(buf, "RssAnon: " SSIZE_FORMAT " kB", &rssanon) == 1) ||
-           (rssfile == -1   && sscanf(buf, "RssFile: " SSIZE_FORMAT " kB", &rssfile) == 1) ||
-           (rssshmem == -1  && sscanf(buf, "RssShmem: " SSIZE_FORMAT " kB", &rssshmem) == 1)
+      if ( (info->vmsize == -1    && sscanf(buf, "VmSize: " SSIZE_FORMAT " kB", &info->vmsize) == 1) ||
+           (info->vmpeak == -1    && sscanf(buf, "VmPeak: " SSIZE_FORMAT " kB", &info->vmpeak) == 1) ||
+           (info->vmswap == -1    && sscanf(buf, "VmSwap: " SSIZE_FORMAT " kB", &info->vmswap) == 1) ||
+           (info->vmhwm == -1     && sscanf(buf, "VmHWM: " SSIZE_FORMAT " kB", &info->vmhwm) == 1) ||
+           (info->vmrss == -1     && sscanf(buf, "VmRSS: " SSIZE_FORMAT " kB", &info->vmrss) == 1) ||
+           (info->rssanon == -1   && sscanf(buf, "RssAnon: " SSIZE_FORMAT " kB", &info->rssanon) == 1) || // Needs Linux 4.5
+           (info->rssfile == -1   && sscanf(buf, "RssFile: " SSIZE_FORMAT " kB", &info->rssfile) == 1) || // Needs Linux 4.5
+           (info->rssshmem == -1  && sscanf(buf, "RssShmem: " SSIZE_FORMAT " kB", &info->rssshmem) == 1)  // Needs Linux 4.5
            )
       {
         num_found ++;
       }
     }
     fclose(f);
+    return true;
+  }
+  return false;
+}
+
+void os::Linux::print_process_memory_info(outputStream* st) {

-    st->print_cr("Virtual Size: " SSIZE_FORMAT "K (peak: " SSIZE_FORMAT "K)", vmsize, vmpeak);
-    st->print("Resident Set Size: " SSIZE_FORMAT "K (peak: " SSIZE_FORMAT "K)", vmrss, vmhwm);
-    if (rssanon != -1) { // requires kernel >= 4.5
+  st->print_cr("Process Memory:");
+
+  // Print virtual and resident set size; peak values; swap; and for
+  //  rss its components if the kernel is recent enough.
+  meminfo_t info;
+  if (query_process_memory_info(&info)) {
+    st->print_cr("Virtual Size: " SSIZE_FORMAT "K (peak: " SSIZE_FORMAT "K)", info.vmsize, info.vmpeak);
+    st->print("Resident Set Size: " SSIZE_FORMAT "K (peak: " SSIZE_FORMAT "K)", info.vmrss, info.vmhwm);
+    if (info.rssanon != -1) { // requires kernel >= 4.5
       st->print(" (anon: " SSIZE_FORMAT "K, file: " SSIZE_FORMAT "K, shmem: " SSIZE_FORMAT "K)",
-                  rssanon, rssfile, rssshmem);
+                info.rssanon, info.rssfile, info.rssshmem);
     }
     st->cr();
-    if (vmswap != -1) { // requires kernel >= 2.6.34
-      st->print_cr("Swapped out: " SSIZE_FORMAT "K", vmswap);
+    if (info.vmswap != -1) { // requires kernel >= 2.6.34
+      st->print_cr("Swapped out: " SSIZE_FORMAT "K", info.vmswap);
     }
   } else {
     st->print_cr("Could not open /proc/self/status to get process memory related information");
@@ -2344,7 +2351,7 @@ void os::Linux::print_process_memory_info(outputStream* st) {
   const size_t total_allocated = (size_t)(unsigned)mi.uordblks;
   st->print("C-Heap outstanding allocations: " SIZE_FORMAT "K", total_allocated / K);
   // Since mallinfo members are int, glibc values may have wrapped. Warn about this.
-  if ((vmrss * K) > UINT_MAX && (vmrss * K) > (total_allocated + UINT_MAX)) {
+  if ((info.vmrss * K) > UINT_MAX && (info.vmrss * K) > (total_allocated + UINT_MAX)) {
     st->print(" (may have wrapped)");
   }
   st->cr();
diff --git a/hotspot/src/os/linux/vm/os_linux.hpp b/hotspot/src/os/linux/vm/os_linux.hpp
index 066b03a..2c4efff 100644
--- a/hotspot/src/os/linux/vm/os_linux.hpp
+++ b/hotspot/src/os/linux/vm/os_linux.hpp
@@ -243,6 +243,23 @@ class Linux {
   public:
   static pthread_condattr_t* condAttr() { return _condattr; }

+  // Output structure for query_process_memory_info()
+  struct meminfo_t {
+    ssize_t vmsize;     // current virtual size
+    ssize_t vmpeak;     // peak virtual size
+    ssize_t vmrss;      // current resident set size
+    ssize_t vmhwm;      // peak resident set size
+    ssize_t vmswap;     // swapped out
+    ssize_t rssanon;    // resident set size (anonymous mappings, needs 4.5)
+    ssize_t rssfile;    // resident set size (file mappings, needs 4.5)
+    ssize_t rssshmem;   // resident set size (shared mappings, needs 4.5)
+  };
+
+  // Attempts to query memory information about the current process and return it in the output structure.
+  // May fail (returns false) or succeed (returns true) but not all output fields are available; unavailable
+  // fields will contain -1.
+  static bool query_process_memory_info(meminfo_t* info);
+
   // Stack repair handling

   // none present
diff --git a/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp b/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp
new file mode 100644
index 0000000..95d03d9
--- /dev/null
+++ b/hotspot/src/os/linux/vm/trimCHeapDCmd.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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 "runtime/os.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/ostream.hpp"
+#include "trimCHeapDCmd.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 (!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/linux/vm/trimCHeapDCmd.hpp b/hotspot/src/os/linux/vm/trimCHeapDCmd.hpp
new file mode 100644
index 0000000..4c5b5cc
--- /dev/null
+++ b/hotspot/src/os/linux/vm/trimCHeapDCmd.hpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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 OS_LINUX_TRIMCHEAPDCMD_HPP
+#define OS_LINUX_TRIMCHEAPDCMD_HPP
+
+#include "services/diagnosticCommand.hpp"
+
+class outputStream;
+
+class TrimCLibcHeapDCmd : public DCmd {
+public:
+  TrimCLibcHeapDCmd(outputStream* output, bool heap) : DCmd(output, heap) {}
+  static const char* name() {
+    return "System.trim_native_heap";
+  }
+  static const char* description() {
+    return "Attempts to free up memory by trimming the C-heap.";
+  }
+  static const char* impact() {
+    return "Low";
+  }
+  static const JavaPermission permission() {
+    JavaPermission p = { "java.lang.management.ManagementPermission", "control", NULL };
+    return p;
+  }
+  virtual void execute(DCmdSource source, TRAPS);
+};
+
+#endif // OS_LINUX_TRIMCHEAPDCMD_HPP
diff --git a/hotspot/src/share/vm/services/diagnosticCommand.cpp b/hotspot/src/share/vm/services/diagnosticCommand.cpp
index 358ec6e..60417b5 100644
--- a/hotspot/src/share/vm/services/diagnosticCommand.cpp
+++ b/hotspot/src/share/vm/services/diagnosticCommand.cpp
@@ -36,6 +36,10 @@
 #include "utilities/macros.hpp"
 #include "oops/objArrayOop.hpp"

+#ifdef LINUX
+#include "trimCHeapDCmd.hpp"
+#endif
+
 PRAGMA_FORMAT_MUTE_WARNINGS_FOR_GCC

 void DCmdRegistrant::register_dcmds(){
@@ -65,6 +69,9 @@ void DCmdRegistrant::register_dcmds(){
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<RotateGCLogDCmd>(full_export, true, false));
   DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderStatsDCmd>(full_export, true, false));
+#ifdef LINUX
+  DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<TrimCLibcHeapDCmd>(full_export, true, false));
+#endif // LINUX

   // Enhanced JMX Agent Support
   // These commands won't be exported via the DiagnosticCommandMBean until an
diff --git a/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java b/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java
new file mode 100644
index 0000000..0fe8e35
--- /dev/null
+++ b/hotspot/test/serviceability/dcmd/TrimLibcHeapTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2021 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.
+ *
+ * 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.
+ */
+
+import org.testng.annotations.Test;
+import com.oracle.java.testlibrary.*;
+
+/*
+ * @test
+ * @summary Test of diagnostic command VM.trim_libc_heap
+ * @library /testlibrary
+ * @requires os.family == "linux"
+ * @modules java.base/jdk.internal.misc
+ *          java.compiler
+ *          java.management
+ *          jdk.internal.jvmstat/sun.jvmstat.monitor
+ * @run testng TrimLibcHeapTest
+ */
+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)");
+        }
+    }
+
+    @Test
+    public void jmx() {
+        run(new JMXExecutor());
+    }
+}
diff --git a/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutor.java b/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutor.java
new file mode 100644
index 0000000..e95a437
--- /dev/null
+++ b/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutor.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2015, 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
+ * 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 com.oracle.java.testlibrary;
+
+/**
+ * Abstract base class for Diagnostic Command executors
+ */
+public abstract class CommandExecutor {
+
+    /**
+     * Execute a diagnostic command
+     *
+     * @param cmd The diagnostic command to execute
+     * @return an {@link jdk.testlibrary.OutputAnalyzer} encapsulating the output of the command
+     * @throws CommandExecutorException if there is an exception on the "calling side" while trying to execute the
+     *          Diagnostic Command. Exceptions thrown on the remote side are available as textual representations in
+     *          stderr, regardless of the specific executor used.
+     */
+    public final OutputAnalyzer execute(String cmd) throws CommandExecutorException {
+        return execute(cmd, false);
+    }
+
+    /**
+     * Execute a diagnostic command
+     *
+     * @param cmd The diagnostic command to execute
+     * @param silent Do not print the command output
+     * @return an {@link jdk.testlibrary.OutputAnalyzer} encapsulating the output of the command
+     * @throws CommandExecutorException if there is an exception on the "calling side" while trying to execute the
+     *          Diagnostic Command. Exceptions thrown on the remote side are available as textual representations in
+     *          stderr, regardless of the specific executor used.
+     */
+    public final OutputAnalyzer execute(String cmd, boolean silent) throws CommandExecutorException {
+        if (!silent) {
+            System.out.printf("Running DCMD '%s' through '%s'%n", cmd, this.getClass().getSimpleName());
+        }
+
+        OutputAnalyzer oa = executeImpl(cmd);
+
+        if (!silent) {
+            System.out.println("---------------- stdout ----------------");
+            System.out.println(oa.getStdout());
+            System.out.println("---------------- stderr ----------------");
+            System.out.println(oa.getStderr());
+            System.out.println("----------------------------------------");
+            System.out.println();
+        }
+        return oa;
+    }
+
+    protected abstract OutputAnalyzer executeImpl(String cmd) throws CommandExecutorException;
+}
diff --git a/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutorException.java b/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutorException.java
new file mode 100644
index 0000000..1857a23
--- /dev/null
+++ b/hotspot/test/testlibrary/com/oracle/java/testlibrary/CommandExecutorException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015, 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
+ * 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 com.oracle.java.testlibrary;
+
+/**
+ * CommandExecutorException encapsulates exceptions thrown (on the "calling side") from the execution of Diagnostic
+ * Commands
+ */
+public class CommandExecutorException extends RuntimeException {
+    private static final long serialVersionUID = -7039597746579144280L;
+
+    public CommandExecutorException(String message, Throwable e) {
+        super(message, e);
+    }
+}
diff --git a/hotspot/test/testlibrary/com/oracle/java/testlibrary/JMXExecutor.java b/hotspot/test/testlibrary/com/oracle/java/testlibrary/JMXExecutor.java
new file mode 100644
index 0000000..317fc5c
--- /dev/null
+++ b/hotspot/test/testlibrary/com/oracle/java/testlibrary/JMXExecutor.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2015, 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
+ * 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 com.oracle.java.testlibrary;
+
+import javax.management.*;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import java.lang.management.ManagementFactory;
+
+import java.util.HashMap;
+
+/**
+ * Executes Diagnostic Commands on the target VM (specified by a host/port combination or a full JMX Service URL) using
+ * the JMX interface. If the target is not the current VM, the JMX Remote interface must be enabled beforehand.
+ */
+public class JMXExecutor extends CommandExecutor {
+
+    private final MBeanServerConnection mbs;
+
+    /**
+     * Instantiates a new JMXExecutor targeting the current VM
+     */
+    public JMXExecutor() {
+        super();
+        mbs = ManagementFactory.getPlatformMBeanServer();
+    }
+
+    /**
+     * Instantiates a new JMXExecutor targeting the VM indicated by the given host/port combination or a full JMX
+     * Service URL
+     *
+     * @param target a host/port combination on the format "host:port" or a full JMX Service URL of the target VM
+     */
+    public JMXExecutor(String target) {
+        String urlStr;
+
+        if (target.matches("^\\w[\\w\\-]*(\\.[\\w\\-]+)*:\\d+$")) {
+            /* Matches "hostname:port" */
+            urlStr = String.format("service:jmx:rmi:///jndi/rmi://%s/jmxrmi", target);
+        } else if (target.startsWith("service:")) {
+            urlStr = target;
+        } else {
+            throw new IllegalArgumentException("Could not recognize target string: " + target);
+        }
+
+        try {
+            JMXServiceURL url = new JMXServiceURL(urlStr);
+            JMXConnector c = JMXConnectorFactory.connect(url, new HashMap<>());
+            mbs = c.getMBeanServerConnection();
+        } catch (IOException e) {
+            throw new CommandExecutorException("Could not initiate connection to target: " + target, e);
+        }
+    }
+
+    protected OutputAnalyzer executeImpl(String cmd) throws CommandExecutorException {
+        String stdout = "";
+        String stderr = "";
+
+        String[] cmdParts = cmd.split(" ", 2);
+        String operation = commandToMethodName(cmdParts[0]);
+        Object[] dcmdArgs = produceArguments(cmdParts);
+        String[] signature = {String[].class.getName()};
+
+        ObjectName beanName = getMBeanName();
+
+        try {
+            stdout = (String) mbs.invoke(beanName, operation, dcmdArgs, signature);
+        }
+
+        /* Failures on the "local" side, the one invoking the command. */
+        catch (ReflectionException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof NoSuchMethodException) {
+                /* We want JMXExecutor to match the behavior of the other CommandExecutors */
+                String message = "Unknown diagnostic command: " + operation;
+                stderr = exceptionTraceAsString(new IllegalArgumentException(message, e));
+            } else {
+                rethrowExecutorException(operation, dcmdArgs, e);
+            }
+        }
+
+        /* Failures on the "local" side, the one invoking the command. */
+        catch (InstanceNotFoundException | IOException e) {
+            rethrowExecutorException(operation, dcmdArgs, e);
+        }
+
+        /* Failures on the remote side, the one executing the invoked command. */
+        catch (MBeanException e) {
+            stdout = exceptionTraceAsString(e);
+        }
+
+        return new OutputAnalyzer(stdout, stderr);
+    }
+
+    private void rethrowExecutorException(String operation, Object[] dcmdArgs,
+                                          Exception e) throws CommandExecutorException {
+        String message = String.format("Could not invoke: %s %s", operation,
+                String.join(" ", (String[]) dcmdArgs[0]));
+        throw new CommandExecutorException(message, e);
+    }
+
+    private ObjectName getMBeanName() throws CommandExecutorException {
+        String MBeanName = "com.sun.management:type=DiagnosticCommand";
+
+        try {
+            return new ObjectName(MBeanName);
+        } catch (MalformedObjectNameException e) {
+            String message = "MBean not found: " + MBeanName;
+            throw new CommandExecutorException(message, e);
+        }
+    }
+
+    private Object[] produceArguments(String[] cmdParts) {
+        Object[] dcmdArgs = {new String[0]}; /* Default: No arguments */
+
+        if (cmdParts.length == 2) {
+            dcmdArgs[0] = cmdParts[1].split(" ");
+        }
+        return dcmdArgs;
+    }
+
+    /**
+     * Convert from diagnostic command to MBean method name
+     *
+     * Examples:
+     * help            --> help
+     * VM.version      --> vmVersion
+     * VM.command_line --> vmCommandLine
+     */
+    private static String commandToMethodName(String cmd) {
+        String operation = "";
+        boolean up = false; /* First letter is to be lower case */
+
+        /*
+         * If a '.' or '_' is encountered it is not copied,
+         * instead the next character will be converted to upper case
+         */
+        for (char c : cmd.toCharArray()) {
+            if (('.' == c) || ('_' == c)) {
+                up = true;
+            } else if (up) {
+                operation = operation.concat(Character.toString(c).toUpperCase());
+                up = false;
+            } else {
+                operation = operation.concat(Character.toString(c).toLowerCase());
+            }
+        }
+
+        return operation;
+    }
+
+    private static String exceptionTraceAsString(Throwable cause) {
+        StringWriter sw = new StringWriter();
+        cause.printStackTrace(new PrintWriter(sw));
+        return sw.toString();
+    }
+
+}
--
1.8.3.1
