humble-video-noarch/src/main/java/io/humble/ferry/JNIMemoryAllocator.java
public byte[] malloc(int size)
  {
    byte[] retval = null;
    // first check the parachute
    JNIMemoryParachute.getParachute().packChute();
    try
    {
      if (SHOULD_RETRY_FAILED_ALLOCS)
      {
        int allocationAttempts = 0;
        int backoffTimeout = 10; // start at 10 milliseconds
        while (true)
        {
          try
          {
            // log.debug("attempting malloc of size: {}", size);
            retval = new byte[size];
            // log.debug("malloced block of size: {}", size);
            // we succeed, so break out
            break;
          }
          catch (final OutOfMemoryError e)
          {
            // try clearing our queue now and do it again. Why?
            // because the first failure may have allowed us to
            // catch a RefCounted no longer in use, and the second
            // attempt may have freed that memory.

            // do a JNI collect before the alloc
            ++allocationAttempts;
            if (allocationAttempts >= MAX_ALLOCATION_ATTEMPTS)
            {
              // try pulling our rip cord
              JNIMemoryParachute.getParachute().pullCord();
              // do one last "hope gc" to free our own memory
              JNIReference.getMgr().gcInternal();
              // and throw the error back to the native code
              throw e;
            }

            log.debug("retrying ({}) allocation of {} bytes",
                allocationAttempts, size);
            try
            {
              // give the finalizer a chance
              if (allocationAttempts <= 1)
              {
                // first just yield
                Thread.yield();
              }
              else
              {
                Thread.sleep(backoffTimeout);
                // and slowly get longer...
                backoffTimeout = (int) (backoffTimeout * FALLBACK_TIME_DECAY);
              }
            }
            catch (InterruptedException e1)
            {
              // reset the interruption so underlying
              // code can also interrupt
              Thread.currentThread().interrupt();
              // and throw the error condition
              throw e;
            }
            // do a JNI collect before the alloc
            JNIReference.getMgr().gcInternal();
          }
        }
      }
      else
      {
        retval = new byte[size];
      }
      addToBuffer(retval);
      retval[retval.length - 1] = 0;
      
//      log.debug("malloc: {}({}:{})", new Object[]
//      {
//          retval.hashCode(), retval.length, size
//      });
      
    }
    catch (Throwable t)
    {
      // do not let an exception leak out since we go back to native code.
      retval = null;
    }
    return retval;
  }
public byte[] malloc(int size)
  {
    byte[] retval = null;
    // first check the parachute
    JNIMemoryParachute.getParachute().packChute();
    try
    {
      if (SHOULD_RETRY_FAILED_ALLOCS)
      {
        int allocationAttempts = 0;
        int backoffTimeout = 10; // start at 10 milliseconds
        while (true)
        {
          try
          {
            // log.debug("attempting malloc of size: {}", size);
            retval = new byte[size];
            // log.debug("malloced block of size: {}", size);
            // we succeed, so break out
            break;
          }
          catch (final OutOfMemoryError e)
          {
            // try clearing our queue now and do it again. Why?
            // because the first failure may have allowed us to
            // catch a RefCounted no longer in use, and the second
            // attempt may have freed that memory.

            // do a JNI collect before the alloc
            ++allocationAttempts;
            if (allocationAttempts >= MAX_ALLOCATION_ATTEMPTS)
            {
              // try pulling our rip cord
              JNIMemoryParachute.getParachute().pullCord();
              // do one last "hope gc" to free our own memory
              JNIReference.getMgr().gcInternal();
              // and throw the error back to the native code
              throw e;
            }

            log.debug("retrying ({}) allocation of {} bytes",
                allocationAttempts, size);
            try
            {
              // give the finalizer a chance
              if (allocationAttempts <= 1)
              {
                // first just yield
                Thread.yield();
              }
              else
              {
                Thread.sleep(backoffTimeout);
                // and slowly get longer...
                backoffTimeout = (int) (backoffTimeout * FALLBACK_TIME_DECAY);
              }
            }
            catch (InterruptedException e1)
            {
              // reset the interruption so underlying
              // code can also interrupt
              Thread.currentThread().interrupt();
              // and throw the error condition
              throw e;
            }
            // do a JNI collect before the alloc
            JNIReference.getMgr().gcInternal();
          }
        }
      }
      else
      {
        retval = new byte[size];
      }
      addToBuffer(retval);
      retval[retval.length - 1] = 0;
      
//      log.debug("malloc: {}({}:{})", new Object[]
//      {
//          retval.hashCode(), retval.length, size
//      });
      
    }
    catch (Throwable t)
    {
      // do not let an exception leak out since we go back to native code.
      retval = null;
    }
    return retval;
  }
Internal Only.  Allocate a new block of bytes. Called from native code.
<p>
Will retry many times if it can't get memory, backing off
in timeouts to get there.
</p>
<p>
Callers must eventually call {@link #free(byte[])} when done
with the bytes or a leak will result.
</p>
@param size
# of bytes requested
@return An array of size or more bytes long, or null on failure.