--- old/src/share/classes/java/nio/Bits.java Fri Oct 15 12:27:56 2010 +++ new/src/share/classes/java/nio/Bits.java Fri Oct 15 12:27:56 2010 @@ -622,7 +622,7 @@ // initialization if it is launched with "-XX:MaxDirectMemorySize=". private static volatile long maxMemory = VM.maxDirectMemory(); private static volatile long reservedMemory; - private static volatile long usedMemory; + private static volatile long totalCapacity; private static volatile long count; private static boolean memoryLimitSet = false; @@ -630,15 +630,17 @@ // freed. They allow the user to control the amount of direct memory // which a process may access. All sizes are specified in bytes. static void reserveMemory(long size, int cap) { - synchronized (Bits.class) { if (!memoryLimitSet && VM.isBooted()) { maxMemory = VM.maxDirectMemory(); memoryLimitSet = true; } - if (size <= maxMemory - reservedMemory) { + // -XX:MaxDirectMemorySize limits the total capacity rather than the + // actual memory usage, which will differ when buffers are page + // aligned. + if (cap <= maxMemory - totalCapacity) { reservedMemory += size; - usedMemory += cap; + totalCapacity += cap; count++; return; } @@ -652,10 +654,10 @@ Thread.currentThread().interrupt(); } synchronized (Bits.class) { - if (reservedMemory + size > maxMemory) + if (totalCapacity + cap > maxMemory) throw new OutOfMemoryError("Direct buffer memory"); reservedMemory += size; - usedMemory += cap; + totalCapacity += cap; count++; } @@ -664,7 +666,7 @@ static synchronized void unreserveMemory(long size, int cap) { if (reservedMemory > 0) { reservedMemory -= size; - usedMemory -= cap; + totalCapacity -= cap; count--; assert (reservedMemory > -1); } @@ -689,7 +691,7 @@ } @Override public long getTotalCapacity() { - return Bits.usedMemory; + return Bits.totalCapacity; } @Override public long getMemoryUsed() { --- old/src/share/classes/java/nio/Direct-X-Buffer.java.template Fri Oct 15 12:27:58 2010 +++ new/src/share/classes/java/nio/Direct-X-Buffer.java.template Fri Oct 15 12:27:57 2010 @@ -29,6 +29,7 @@ import sun.misc.Cleaner; import sun.misc.Unsafe; +import sun.misc.VM; import sun.nio.ch.DirectBuffer; @@ -114,8 +115,9 @@ Direct$Type$Buffer$RW$(int cap) { // package-private #if[rw] super(-1, 0, cap, cap, false); + boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); - int size = cap + ps; + long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; @@ -126,7 +128,7 @@ throw x; } unsafe.setMemory(base, size, (byte) 0); - if (base % ps != 0) { + if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { --- old/src/share/classes/sun/misc/VM.java Fri Oct 15 12:28:00 2010 +++ new/src/share/classes/sun/misc/VM.java Fri Oct 15 12:27:59 2010 @@ -178,6 +178,17 @@ return directMemory; } + // User-controllable flag that determines if direct buffers should be page + // aligned. The "-XX:+PageAlignDirectMemory" option can be used to force + // buffers, allocated by ByteBuffer.allocateDirect, to be page aligned. + private static boolean pageAlignDirectMemory; + + // Returns {@code true} if the direct buffers should be page aligned. This + // variable is initialized by saveAndRemoveProperties. + public static boolean isDirectMemoryPageAligned() { + return pageAlignDirectMemory; + } + // A user-settable boolean to determine whether ClassLoader.loadClass should // accept array syntax. This value may be changed during VM initialization // via the system property "sun.lang.ClassLoader.allowArraySyntax". @@ -252,6 +263,11 @@ } } + // Check if direct buffers should be page aligned + s = (String)props.remove("sun.nio.PageAlignDirectMemory"); + if ("true".equals(s)) + pageAlignDirectMemory = true; + // Set a boolean to determine whether ClassLoader.loadClass accepts // array syntax. This value is controlled by the system property // "sun.lang.ClassLoader.allowArraySyntax". --- /dev/null Fri Oct 15 12:28:02 2010 +++ new/test/java/nio/Buffer/LimitDirectMemory.java Fri Oct 15 12:28:01 2010 @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2002, 2010, 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 java.nio.ByteBuffer; +import java.util.Properties; + +public class LimitDirectMemory { + private static int K = 1024; + + public static void main(String [] args) throws Exception { + if (args.length < 2) + throw new RuntimeException(); + boolean throwp = parseThrow(args[0]); + int size = parseSize(args[1]); + int incr = (args.length > 2 ? parseSize(args[2]) : size); + + Properties p = System.getProperties(); + if (p.getProperty("sun.nio.MaxDirectMemorySize") != null) + throw new RuntimeException("sun.nio.MaxDirectMemorySize defined"); + + ByteBuffer [] b = new ByteBuffer[K]; + + // Fill up most/all of the direct memory + int i = 0; + while (size >= incr) { + b[i++] = ByteBuffer.allocateDirect(incr); + size -= incr; + } + + if (throwp) { + try { + b[i] = ByteBuffer.allocateDirect(incr); + throw new RuntimeException("OutOfMemoryError not thrown: " + + incr); + } catch (OutOfMemoryError e) { + e.printStackTrace(System.out); + System.out.println("OK - Error thrown as expected "); + } + } else { + b[i] = ByteBuffer.allocateDirect(incr); + System.out.println("OK - Error not thrown"); + } + } + + private static boolean parseThrow(String s) { + if (s.equals("true")) return true; + if (s.equals("false")) return false; + throw new RuntimeException("Unrecognized expectation: " + s); + } + + private static int parseSize(String size) throws Exception { + + if (size.equals("DEFAULT")) + return (int)Runtime.getRuntime().maxMemory(); + if (size.equals("DEFAULT+1")) + return (int)Runtime.getRuntime().maxMemory() + 1; + if (size.equals("DEFAULT+1M")) + return (int)Runtime.getRuntime().maxMemory() + (1 << 20); + if (size.equals("DEFAULT-1")) + return (int)Runtime.getRuntime().maxMemory() - 1; + if (size.equals("DEFAULT/2")) + return (int)Runtime.getRuntime().maxMemory() / 2; + + int idx = 0, len = size.length(); + + int result = 1; + for (int i = 0; i < len; i++) { + if (Character.isDigit(size.charAt(i))) idx++; + else break; + } + + if (idx == 0) + throw new RuntimeException("No digits detected: " + size); + + result = Integer.parseInt(size.substring(0, idx)); + + if (idx < len) { + for (int i = idx; i < len; i++) { + switch(size.charAt(i)) { + case 'T': case 't': result *= K; // fall through + case 'G': case 'g': result *= K; // fall through + case 'M': case 'm': result *= K; // fall through + case 'K': case 'k': result *= K; + break; + default: + throw new RuntimeException("Unrecognized size: " + size); + } + } + } + return result; + } +} --- /dev/null Fri Oct 15 12:28:04 2010 +++ new/test/java/nio/Buffer/LimitDirectMemory.sh Fri Oct 15 12:28:03 2010 @@ -0,0 +1,99 @@ +#!/bin/sh + +# +# Copyright (c) 2002, 2010, 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 +# @bug 4627316 6743526 +# @summary Test new option to limit direct memory allocation +# +# @build LimitDirectMemory +# @run shell LimitDirectMemory.sh + +# set platform-dependent variable +OS=`uname -s` +case "$OS" in + SunOS | Linux ) TMP=/tmp ;; + Windows* ) TMP="c:/temp" ;; + * ) + echo "Unrecognized system!" + exit 1; + ;; +esac + +TMP1=${TMP}/tmp1_$$ + +runTest() { + echo "Testing: $*" + ${TESTJAVA}/bin/java $* + if [ $? -eq 0 ] + then echo "--- passed as expected" + else + echo "--- failed" + exit 1 + fi +} + +launchFail() { + echo "Testing: -XX:MaxDirectMemorySize=$* -cp ${TESTCLASSES} \ + LimitDirectMemory true DEFAULT DEFAULT+1M" + ${TESTJAVA}/bin/java -XX:MaxDirectMemorySize=$* -cp ${TESTCLASSES} \ + LimitDirectMemory true DEFAULT DEFAULT+1M > ${TMP1} 2>&1 + cat ${TMP1} + cat ${TMP1} | grep -s "Unrecognized VM option: \'MaxDirectMemorySize=" + if [ $? -ne 0 ] + then echo "--- failed as expected" + else + echo "--- failed" + exit 1 + fi +} + +# $java LimitDirectMemory throwp fill_direct_memory size_per_buffer + +# Memory is properly limited using multiple buffers. +runTest -XX:MaxDirectMemorySize=10 -cp ${TESTCLASSES} LimitDirectMemory true 10 1 +runTest -XX:MaxDirectMemorySize=1k -cp ${TESTCLASSES} LimitDirectMemory true 1k 100 +runTest -XX:MaxDirectMemorySize=10m -cp ${TESTCLASSES} LimitDirectMemory true 10m 10m + +# We can increase the amount of available memory. +runTest -XX:MaxDirectMemorySize=65M -cp ${TESTCLASSES} \ + LimitDirectMemory false 64M 65M + +# Exactly the default amount of memory is available. +runTest -cp ${TESTCLASSES} LimitDirectMemory false 10 1 +runTest -cp ${TESTCLASSES} LimitDirectMemory false 0 DEFAULT +runTest -cp ${TESTCLASSES} LimitDirectMemory true 0 DEFAULT+1 + +# We should be able to eliminate direct memory allocation entirely. +runTest -XX:MaxDirectMemorySize=0 -cp ${TESTCLASSES} LimitDirectMemory true 0 1 + +# Setting the system property should not work so we should be able to allocate +# the default amount. +runTest -Dsun.nio.MaxDirectMemorySize=1K -cp ${TESTCLASSES} \ + LimitDirectMemory false DEFAULT-1 DEFAULT/2 + +# Various bad values fail to launch the VM. +launchFail foo +launchFail 10kmt +launchFail -1