8009062: poor performance of JNI AttachCurrentThread after fix for 7017193
Summary: don't re-evaluate stack bounds for main thread before install guard page
Reviewed-by: coleenp, dholmes, dlong
--- a/hotspot/src/os/linux/vm/os_linux.cpp Thu Aug 29 10:33:13 2013 -0400
+++ b/hotspot/src/os/linux/vm/os_linux.cpp Thu Aug 29 21:48:23 2013 +0400
@@ -2943,6 +2943,53 @@
return res != (uintptr_t) MAP_FAILED;
}
+static
+address get_stack_commited_bottom(address bottom, size_t size) {
+ address nbot = bottom;
+ address ntop = bottom + size;
+
+ size_t page_sz = os::vm_page_size();
+ unsigned pages = size / page_sz;
+
+ unsigned char vec[1];
+ unsigned imin = 1, imax = pages + 1, imid;
+ int mincore_return_value;
+
+ while (imin < imax) {
+ imid = (imax + imin) / 2;
+ nbot = ntop - (imid * page_sz);
+
+ // Use a trick with mincore to check whether the page is mapped or not.
+ // mincore sets vec to 1 if page resides in memory and to 0 if page
+ // is swapped output but if page we are asking for is unmapped
+ // it returns -1,ENOMEM
+ mincore_return_value = mincore(nbot, page_sz, vec);
+
+ if (mincore_return_value == -1) {
+ // Page is not mapped go up
+ // to find first mapped page
+ if (errno != EAGAIN) {
+ assert(errno == ENOMEM, "Unexpected mincore errno");
+ imax = imid;
+ }
+ } else {
+ // Page is mapped go down
+ // to find first not mapped page
+ imin = imid + 1;
+ }
+ }
+
+ nbot = nbot + page_sz;
+
+ // Adjust stack bottom one page up if last checked page is not mapped
+ if (mincore_return_value == -1) {
+ nbot = nbot + page_sz;
+ }
+
+ return nbot;
+}
+
+
// Linux uses a growable mapping for the stack, and if the mapping for
// the stack guard pages is not removed when we detach a thread the
// stack cannot grow beyond the pages where the stack guard was
@@ -2957,59 +3004,37 @@
// So, we need to know the extent of the stack mapping when
// create_stack_guard_pages() is called.
-// Find the bounds of the stack mapping. Return true for success.
-//
// We only need this for stacks that are growable: at the time of
// writing thread stacks don't use growable mappings (i.e. those
// creeated with MAP_GROWSDOWN), and aren't marked "[stack]", so this
// only applies to the main thread.
-static
-bool get_stack_bounds(uintptr_t *bottom, uintptr_t *top) {
-
- char buf[128];
- int fd, sz;
-
- if ((fd = ::open("/proc/self/maps", O_RDONLY)) < 0) {
- return false;
- }
-
- const char kw[] = "[stack]";
- const int kwlen = sizeof(kw)-1;
-
- // Address part of /proc/self/maps couldn't be more than 128 bytes
- while ((sz = os::get_line_chars(fd, buf, sizeof(buf))) > 0) {
- if (sz > kwlen && ::memcmp(buf+sz-kwlen, kw, kwlen) == 0) {
- // Extract addresses
- if (sscanf(buf, "%" SCNxPTR "-%" SCNxPTR, bottom, top) == 2) {
- uintptr_t sp = (uintptr_t) __builtin_frame_address(0);
- if (sp >= *bottom && sp <= *top) {
- ::close(fd);
- return true;
- }
- }
- }
- }
-
- ::close(fd);
- return false;
-}
-
-
// If the (growable) stack mapping already extends beyond the point
// where we're going to put our guard pages, truncate the mapping at
// that point by munmap()ping it. This ensures that when we later
// munmap() the guard pages we don't leave a hole in the stack
-// mapping. This only affects the main/initial thread, but guard
-// against future OS changes
+// mapping. This only affects the main/initial thread
+
bool os::pd_create_stack_guard_pages(char* addr, size_t size) {
- uintptr_t stack_extent, stack_base;
- bool chk_bounds = NOT_DEBUG(os::Linux::is_initial_thread()) DEBUG_ONLY(true);
- if (chk_bounds && get_stack_bounds(&stack_extent, &stack_base)) {
- assert(os::Linux::is_initial_thread(),
- "growable stack in non-initial thread");
- if (stack_extent < (uintptr_t)addr)
- ::munmap((void*)stack_extent, (uintptr_t)addr - stack_extent);
+
+ if (os::Linux::is_initial_thread()) {
+ // As we manually grow stack up to bottom inside create_attached_thread(),
+ // it's likely that os::Linux::initial_thread_stack_bottom is mapped and
+ // we don't need to do anything special.
+ // Check it first, before calling heavy function.
+ uintptr_t stack_extent = (uintptr_t) os::Linux::initial_thread_stack_bottom();
+ unsigned char vec[1];
+
+ if (mincore((address)stack_extent, os::vm_page_size(), vec) == -1) {
+ // Fallback to slow path on all errors, including EAGAIN
+ stack_extent = (uintptr_t) get_stack_commited_bottom(
+ os::Linux::initial_thread_stack_bottom(),
+ (size_t)addr - stack_extent);
+ }
+
+ if (stack_extent < (uintptr_t)addr) {
+ ::munmap((void*)stack_extent, (uintptr_t)(addr - stack_extent));
+ }
}
return os::commit_memory(addr, size, !ExecMem);
@@ -3018,13 +3043,13 @@
// If this is a growable mapping, remove the guard pages entirely by
// munmap()ping them. If not, just call uncommit_memory(). This only
// affects the main/initial thread, but guard against future OS changes
+// It's safe to always unmap guard pages for initial thread because we
+// always place it right after end of the mapped region
+
bool os::remove_stack_guard_pages(char* addr, size_t size) {
uintptr_t stack_extent, stack_base;
- bool chk_bounds = NOT_DEBUG(os::Linux::is_initial_thread()) DEBUG_ONLY(true);
- if (chk_bounds && get_stack_bounds(&stack_extent, &stack_base)) {
- assert(os::Linux::is_initial_thread(),
- "growable stack in non-initial thread");
-
+
+ if (os::Linux::is_initial_thread()) {
return ::munmap(addr, size) == 0;
}
--- a/hotspot/src/share/vm/runtime/os.cpp Thu Aug 29 10:33:13 2013 -0400
+++ b/hotspot/src/share/vm/runtime/os.cpp Thu Aug 29 21:48:23 2013 +0400
@@ -1424,44 +1424,6 @@
return result;
}
-// Read file line by line, if line is longer than bsize,
-// skip rest of line.
-int os::get_line_chars(int fd, char* buf, const size_t bsize){
- size_t sz, i = 0;
-
- // read until EOF, EOL or buf is full
- while ((sz = (int) read(fd, &buf[i], 1)) == 1 && i < (bsize-2) && buf[i] != '\n') {
- ++i;
- }
-
- if (buf[i] == '\n') {
- // EOL reached so ignore EOL character and return
-
- buf[i] = 0;
- return (int) i;
- }
-
- buf[i+1] = 0;
-
- if (sz != 1) {
- // EOF reached. if we read chars before EOF return them and
- // return EOF on next call otherwise return EOF
-
- return (i == 0) ? -1 : (int) i;
- }
-
- // line is longer than size of buf, skip to EOL
- char ch;
- while (read(fd, &ch, 1) == 1 && ch != '\n') {
- // Do nothing
- }
-
- // return initial part of line that fits in buf.
- // If we reached EOF, it will be returned on next call.
-
- return (int) i;
-}
-
void os::SuspendedThreadTask::run() {
assert(Threads_lock->owned_by_self() || (_thread == VMThread::vm_thread()), "must have threads lock to call this");
internal_do_task();
--- a/hotspot/src/share/vm/runtime/os.hpp Thu Aug 29 10:33:13 2013 -0400
+++ b/hotspot/src/share/vm/runtime/os.hpp Thu Aug 29 21:48:23 2013 +0400
@@ -725,10 +725,6 @@
// Hook for os specific jvm options that we don't want to abort on seeing
static bool obsolete_option(const JavaVMOption *option);
- // Read file line by line. If line is longer than bsize,
- // rest of line is skipped. Returns number of bytes read or -1 on EOF
- static int get_line_chars(int fd, char *buf, const size_t bsize);
-
// Extensions
#include "runtime/os_ext.hpp"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/InitialThreadOverflow/DoOverflow.java Thu Aug 29 21:48:23 2013 +0400
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2013, 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.
+ */
+
+public class DoOverflow {
+
+ static int count;
+
+ public void overflow() {
+ count+=1;
+ overflow();
+ }
+
+ public static void printIt() {
+ System.out.println("Going to overflow stack");
+ try {
+ new DoOverflow().overflow();
+ } catch(java.lang.StackOverflowError e) {
+ System.out.println("Overflow OK " + count);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/InitialThreadOverflow/invoke.cxx Thu Aug 29 21:48:23 2013 +0400
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2013, 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 <assert.h>
+#include <jni.h>
+
+#include <pthread.h>
+
+JavaVM* jvm;
+
+void *
+floobydust (void *p) {
+ JNIEnv *env;
+
+ jvm->AttachCurrentThread((void**)&env, NULL);
+
+ jclass class_id = env->FindClass ("DoOverflow");
+ assert (class_id);
+
+ jmethodID method_id = env->GetStaticMethodID(class_id, "printIt", "()V");
+ assert (method_id);
+
+ env->CallStaticVoidMethod(class_id, method_id, NULL);
+
+ jvm->DetachCurrentThread();
+}
+
+int
+main (int argc, const char** argv) {
+ JavaVMOption options[1];
+ options[0].optionString = (char*) "-Xss320k";
+
+ JavaVMInitArgs vm_args;
+ vm_args.version = JNI_VERSION_1_2;
+ vm_args.ignoreUnrecognized = JNI_TRUE;
+ vm_args.options = options;
+ vm_args.nOptions = 1;
+
+ JNIEnv* env;
+ jint result = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
+ assert(result >= 0);
+
+ pthread_t thr;
+ pthread_create(&thr, NULL, floobydust, NULL);
+ pthread_join(thr, NULL);
+
+ floobydust(NULL);
+
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/InitialThreadOverflow/testme.sh Thu Aug 29 21:48:23 2013 +0400
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Copyright (c) 2013 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.
+
+# @test testme.sh
+# @bug 8009062
+# @summary Poor performance of JNI AttachCurrentThread after fix for 7017193
+# @compile DoOverflow.java
+# @run shell testme.sh
+
+set -x
+if [ "${TESTSRC}" = "" ]
+then
+ TESTSRC=${PWD}
+ echo "TESTSRC not set. Using "${TESTSRC}" as default"
+fi
+echo "TESTSRC=${TESTSRC}"
+## Adding common setup Variables for running shell tests.
+. ${TESTSRC}/../../test_env.sh
+
+if [ "${VM_OS}" != "linux" ]
+then
+ echo "Test only valid for Linux"
+ exit 0
+fi
+
+gcc_cmd=`which gcc`
+if [ "x$gcc_cmd" == "x" ]; then
+ echo "WARNING: gcc not found. Cannot execute test." 2>&1
+ exit 0;
+fi
+
+CFLAGS="-m${VM_BITS}"
+
+LD_LIBRARY_PATH=.:${COMPILEJAVA}/jre/lib/${VM_CPU}/${VM_TYPE}:/usr/lib:$LD_LIBRARY_PATH
+export LD_LIBRARY_PATH
+
+cp ${TESTSRC}${FS}invoke.cxx .
+
+# Copy the result of our @compile action:
+cp ${TESTCLASSES}${FS}DoOverflow.class .
+
+echo "Compilation flag: ${COMP_FLAG}"
+# Note pthread may not be found thus invoke creation will fail to be created.
+# Check to ensure you have a /usr/lib/libpthread.so if you don't please look
+# for /usr/lib/`uname -m`-linux-gnu version ensure to add that path to below compilation.
+
+$gcc_cmd -DLINUX ${CFLAGS} -o invoke \
+ -I${COMPILEJAVA}/include -I${COMPILEJAVA}/include/linux \
+ -L${COMPILEJAVA}/jre/lib/${VM_CPU}/${VM_TYPE} \
+ -ljvm -lpthread invoke.cxx
+
+./invoke
+exit $?