Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-7027845

(bf) ByteBuffer.allocateDirect() throws OutOfMemoryError unexpectedly

    Details

    • Type: Bug
    • Status: Closed
    • Priority: P4
    • Resolution: Duplicate
    • Affects Version/s: 6u24
    • Fix Version/s: None
    • Component/s: core-libs
    • Labels:
    • Subcomponent:
    • CPU:
      x86
    • OS:
      windows_7

      Description

      FULL PRODUCT VERSION :
      java version "1.6.0_24"
      Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
      Java HotSpot(TM) Client VM (build 19.1-b02, mixed mode, sharing)

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 6.1.7601]

      EXTRA RELEVANT SYSTEM CONFIGURATION :
      Specific to 32-bit JRE

      A DESCRIPTION OF THE PROBLEM :
      Any application that allocates native memory (or uses a library that does) risks getting OutOfMemoryError when invoking ByteBuffer.allocateDirect().

      1. According to http://msdn.microsoft.com/en-us/library/aa366778%28v=vs.85%29.aspx#memory_limits 32-bit processes are limited to 2GB of RAM.

      2. Memory allocated by native code counts against this limit

      3. java.nio.Bits.reserveMemory() attempts to guarantee that the GC will be invoked before throwing OutOfMemoryError when allocating direct buffers. However, this method ignores the fact that malloc() may throw OutOfMemoryError earlier than expected (because of memory allocated by native code push us over the 2GB limit).

      4. ByteBuffer.allocateDirect() attempts to allocate memory, malloc() hits the 2GB process-space limit and throws OutOfMemoryError. There is plenty of memory ready to be freed up, but the JVM never invokes the garbage collector.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run testcase

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Attempt to free up memory using the garbage collector before throwing OutOfMemoryError
      ACTUAL -
      OutOfMemoryError thrown in spite of the fact that there is sufficient memory once a garbage collector cycle is run

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Exception in thread "main" java.lang.OutOfMemoryError
              at sun.misc.Unsafe.allocateMemory(Native Method)
              at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:102)
              at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import java.lang.reflect.Field;
      import java.nio.Buffer;
      import java.nio.ByteBuffer;
      import java.util.ArrayList;
      import java.util.List;
      import sun.misc.Unsafe;

      /**
       * @author Gili Tzabari
       */
      public class Main {

          private static final boolean workaround = false;

          /**
           * @param args the command line arguments
           */
          public static void main(String[] args) {

              // DESCRIPTION:
              //
              // The JVM is initialized using the following command-line options:
              // -Xms500m -Xmx500m -XX:MaxDirectMemorySize=500m
              //
              // The maximum heap size is 500MB. The maximum direct-buffers size is 500MB.
              //
              // This testcase allocates 1GB of native memory and 500MB of heap. As the direct buffers
              // grow malloc() will hit the 2gb process memory limit and throw OutOfMemoryError.
              // Because the JVM does not anticipate this (the 32-bit memory limit) it doesn't trigger
              // the garbage collector before throwing OutOfMemoryError.
              //
              // Only 100 out of the 500MB of direct buffers are strongly-referenced. Has the garbage
              // collector been triggered, it would have reduced the overall memory usage under the
              // 2GB limit and OutOfMemoryError would not have been thrown.
              Class<?> c = null;
              Field maxMemory = null;
              Field reservedMemory = null;
              try {
                  c = Class.forName("java.nio.Bits");
                  maxMemory = c.getDeclaredField("maxMemory");
                  maxMemory.setAccessible(true);
                  reservedMemory = c.getDeclaredField("reservedMemory");
                  reservedMemory.setAccessible(true);
              } catch (IllegalArgumentException e) {
                  e.printStackTrace();
              } catch (NoSuchFieldException e) {
                  e.printStackTrace();
              } catch (SecurityException e) {
                  e.printStackTrace();
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              }

              Unsafe unsafe;
              try {
                  Field field = Unsafe.class.getDeclaredField("theUnsafe");
                  field.setAccessible(true);
                  unsafe = (Unsafe) field.get(null);
              } catch (Exception ex) {
                  throw new RuntimeException("can't get Unsafe instance", ex);
              }

              // Simulate an application or library allocating 1GB of native memory using JNI.
              for (int i = 0; i < 10; ++i) {
                  // Allocate small chunks because memory fragmentation prevents us from allocating
                  // 1gb of contiguous memory.
                  unsafe.allocateMemory(1024 * 1024 * 100); // 100m
              }

              List<Buffer> directBuffers = new ArrayList<Buffer>();
              while (true) {
                  if (workaround) {
                      System.gc();
                  }

                  // Each buffer is 1mb
                  ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024 * 1000);

                  // Strongly reference 100 out of 500MB of direct buffers
                  directBuffers.add(directBuffer);
                  if (directBuffers.size() >= 100) {
                      directBuffers.remove(0);
                  }

                  try {
                      synchronized (c) {
                          Long maxMemoryValue = (Long) maxMemory.get(null);
                          Long reservedMemoryValue = (Long) reservedMemory.get(null);
                          System.out.println("Direct Memory");
                          System.out.println("max : " + maxMemoryValue);
                          System.out.println("free: " + (maxMemoryValue - reservedMemoryValue));
                      }
                  } catch (IllegalArgumentException e) {
                      e.printStackTrace();
                  } catch (IllegalAccessException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Run System.gc() before ByteBuffer.allocateDirect(), which is highly inefficient.

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                Unassigned
                Reporter:
                webbuggrp Webbug Group
              • Votes:
                0 Vote for this issue
                Watchers:
                0 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved:
                  Imported:
                  Indexed: