TestMaxCachedBufferSize.java revision 14606:bc3775e25b52
1/* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24import java.io.IOException; 25 26import java.lang.management.BufferPoolMXBean; 27import java.lang.management.ManagementFactory; 28 29import java.nio.ByteBuffer; 30 31import java.nio.channels.FileChannel; 32 33import java.nio.file.Path; 34import java.nio.file.Paths; 35 36import static java.nio.file.StandardOpenOption.CREATE; 37import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; 38import static java.nio.file.StandardOpenOption.WRITE; 39 40import java.util.List; 41import java.util.Random; 42 43/* 44 * @test 45 * @requires sun.arch.data.model == "64" 46 * @modules java.management 47 * @build TestMaxCachedBufferSize 48 * @run main/othervm TestMaxCachedBufferSize 49 * @run main/othervm -Djdk.nio.maxCachedBufferSize=0 TestMaxCachedBufferSize 50 * @run main/othervm -Djdk.nio.maxCachedBufferSize=2000 TestMaxCachedBufferSize 51 * @run main/othervm -Djdk.nio.maxCachedBufferSize=100000 TestMaxCachedBufferSize 52 * @run main/othervm -Djdk.nio.maxCachedBufferSize=10000000 TestMaxCachedBufferSize 53 * @summary Test the implementation of the jdk.nio.maxCachedBufferSize property. 54 */ 55public class TestMaxCachedBufferSize { 56 private static final int DEFAULT_ITERS = 10 * 1000; 57 private static final int DEFAULT_THREAD_NUM = 4; 58 59 private static final int SMALL_BUFFER_MIN_SIZE = 4 * 1024; 60 private static final int SMALL_BUFFER_MAX_SIZE = 64 * 1024; 61 private static final int SMALL_BUFFER_DIFF_SIZE = 62 SMALL_BUFFER_MAX_SIZE - SMALL_BUFFER_MIN_SIZE; 63 64 private static final int LARGE_BUFFER_MIN_SIZE = 512 * 1024; 65 private static final int LARGE_BUFFER_MAX_SIZE = 4 * 1024 * 1024; 66 private static final int LARGE_BUFFER_DIFF_SIZE = 67 LARGE_BUFFER_MAX_SIZE - LARGE_BUFFER_MIN_SIZE; 68 69 private static final int LARGE_BUFFER_FREQUENCY = 100; 70 71 private static final String FILE_NAME_PREFIX = "nio-out-file-"; 72 private static final int VERBOSE_PERIOD = 5 * 1000; 73 74 private static int iters = DEFAULT_ITERS; 75 private static int threadNum = DEFAULT_THREAD_NUM; 76 77 private static BufferPoolMXBean getDirectPool() { 78 final List<BufferPoolMXBean> pools = 79 ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class); 80 for (BufferPoolMXBean pool : pools) { 81 if (pool.getName().equals("direct")) { 82 return pool; 83 } 84 } 85 throw new Error("could not find direct pool"); 86 } 87 private static final BufferPoolMXBean directPool = getDirectPool(); 88 89 // Each worker will do write operations on a file channel using 90 // buffers of various sizes. The buffer size is randomly chosen to 91 // be within a small or a large range. This way we can control 92 // which buffers can be cached (all, only the small ones, or none) 93 // by setting the jdk.nio.maxCachedBufferSize property. 94 private static class Worker implements Runnable { 95 private final int id; 96 private final Random random = new Random(); 97 private long smallBufferCount = 0; 98 private long largeBufferCount = 0; 99 100 private int getWriteSize() { 101 int minSize = 0; 102 int diff = 0; 103 if (random.nextInt() % LARGE_BUFFER_FREQUENCY != 0) { 104 // small buffer 105 minSize = SMALL_BUFFER_MIN_SIZE; 106 diff = SMALL_BUFFER_DIFF_SIZE; 107 smallBufferCount += 1; 108 } else { 109 // large buffer 110 minSize = LARGE_BUFFER_MIN_SIZE; 111 diff = LARGE_BUFFER_DIFF_SIZE; 112 largeBufferCount += 1; 113 } 114 return minSize + random.nextInt(diff); 115 } 116 117 private void loop() { 118 final String fileName = String.format("%s%d", FILE_NAME_PREFIX, id); 119 120 try { 121 for (int i = 0; i < iters; i += 1) { 122 final int writeSize = getWriteSize(); 123 124 // This will allocate a HeapByteBuffer. It should not 125 // be a direct buffer, otherwise the write() method on 126 // the channel below will not create a temporary 127 // direct buffer for the write. 128 final ByteBuffer buffer = ByteBuffer.allocate(writeSize); 129 130 // Put some random data on it. 131 while (buffer.hasRemaining()) { 132 buffer.put((byte) random.nextInt()); 133 } 134 buffer.rewind(); 135 136 final Path file = Paths.get(fileName); 137 try (FileChannel outChannel = FileChannel.open(file, CREATE, TRUNCATE_EXISTING, WRITE)) { 138 // The write() method will create a temporary 139 // direct buffer for the write and attempt to cache 140 // it. It's important that buffer is not a 141 // direct buffer, otherwise the temporary buffer 142 // will not be created. 143 long res = outChannel.write(buffer); 144 } 145 146 if ((i + 1) % VERBOSE_PERIOD == 0) { 147 System.out.printf( 148 " Worker %3d | %8d Iters | Small %8d Large %8d | Direct %4d / %7dK\n", 149 id, i + 1, smallBufferCount, largeBufferCount, 150 directPool.getCount(), directPool.getTotalCapacity() / 1024); 151 } 152 } 153 } catch (IOException e) { 154 throw new Error("I/O error", e); 155 } 156 } 157 158 @Override 159 public void run() { 160 loop(); 161 } 162 163 public Worker(int id) { 164 this.id = id; 165 } 166 } 167 168 public static void checkDirectBuffers(long expectedCount, long expectedMax) { 169 final long directCount = directPool.getCount(); 170 final long directTotalCapacity = directPool.getTotalCapacity(); 171 System.out.printf("Direct %d / %dK\n", 172 directCount, directTotalCapacity / 1024); 173 174 // Note that directCount could be < expectedCount. This can 175 // happen if a GC occurs after one of the worker threads exits 176 // since its thread-local DirectByteBuffer could be cleaned up 177 // before we reach here. 178 if (directCount > expectedCount) { 179 throw new Error(String.format( 180 "inconsistent direct buffer total count, expected = %d, found = %d", 181 expectedCount, directCount)); 182 } 183 184 if (directTotalCapacity > expectedMax) { 185 throw new Error(String.format( 186 "inconsistent direct buffer total capacity, expectex max = %d, found = %d", 187 expectedMax, directTotalCapacity)); 188 } 189 } 190 191 public static void main(String[] args) { 192 final String maxBufferSizeStr = System.getProperty("jdk.nio.maxCachedBufferSize"); 193 final long maxBufferSize = 194 (maxBufferSizeStr != null) ? Long.valueOf(maxBufferSizeStr) : Long.MAX_VALUE; 195 196 // We assume that the max cannot be equal to a size of a 197 // buffer that can be allocated (makes sanity checking at the 198 // end easier). 199 if ((SMALL_BUFFER_MIN_SIZE <= maxBufferSize && 200 maxBufferSize <= SMALL_BUFFER_MAX_SIZE) || 201 (LARGE_BUFFER_MIN_SIZE <= maxBufferSize && 202 maxBufferSize <= LARGE_BUFFER_MAX_SIZE)) { 203 throw new Error(String.format("max buffer size = %d not allowed", 204 maxBufferSize)); 205 } 206 207 System.out.printf("Threads %d | Iterations %d | MaxBufferSize %d\n", 208 threadNum, iters, maxBufferSize); 209 System.out.println(); 210 211 final Thread[] threads = new Thread[threadNum]; 212 for (int i = 0; i < threadNum; i += 1) { 213 threads[i] = new Thread(new Worker(i)); 214 threads[i].start(); 215 } 216 217 try { 218 for (int i = 0; i < threadNum; i += 1) { 219 threads[i].join(); 220 } 221 } catch (InterruptedException e) { 222 throw new Error("join() interrupted!", e); 223 } 224 225 // There is an assumption here that, at this point, only the 226 // cached DirectByteBuffers should be active. Given we 227 // haven't used any other DirectByteBuffers in this test, this 228 // should hold. 229 // 230 // Also note that we can only do the sanity checking at the 231 // end and not during the run given that, at any time, there 232 // could be buffers currently in use by some of the workers 233 // that will not be cached. 234 235 System.out.println(); 236 if (maxBufferSize < SMALL_BUFFER_MAX_SIZE) { 237 // The max buffer size is smaller than all buffers that 238 // were allocated. No buffers should have been cached. 239 checkDirectBuffers(0, 0); 240 } else if (maxBufferSize < LARGE_BUFFER_MIN_SIZE) { 241 // The max buffer size is larger than all small buffers 242 // but smaller than all large buffers that were 243 // allocated. Only small buffers could have been cached. 244 checkDirectBuffers(threadNum, 245 (long) threadNum * (long) SMALL_BUFFER_MAX_SIZE); 246 } else { 247 // The max buffer size is larger than all buffers that 248 // were allocated. All buffers could have been cached. 249 checkDirectBuffers(threadNum, 250 (long) threadNum * (long) LARGE_BUFFER_MAX_SIZE); 251 } 252 } 253} 254