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