Stuart (and Martin), I absolutely agree with the reasoning. ArraysSupport.newLength() is also probably easier for the compiler to optimize around (no exception.)
I guess the only conflict here is to be meaningfully informative. The bug assigned to me (https://bugs.openjdk.java.net/browse/JDK-8230744 <https://bugs.openjdk.java.net/browse/JDK-8230744>) was really about not getting clear information when the OOM is generated, Getting an OOM from the bowels of the VM doesn't always give a clear picture (even with a backtrace) when the allocate code is distant from the expression that calculated the new size (VM: I can't do this. You figure it out, Developer: ? ). Needed here is something like: ArraysSupport.newLength(oldLength, minGrowth, perfGrowth, () -> "Queue exceeds implementation limit") There are other JDK attempts using addExact/multiplyExact to catch the error closer to action. Note that there are a few cases, ex. StringJoiner, where the growth is a three way sum where any two args could go negative (overflow) and the third arg bringing it positive again. Based on what I see in the JDK, the world would be well served with an Arrays.grow(array, minGrowth, perfGrowth, () -> "Queue exceeds implementation limit"). This differs Arrays.copyOf in 1) does overflow checks 2) provides meaningful message 3) eliminates a common and often poorly implemented code pattern. The alternative to this is catching the OOM at site to replace with a meaningful message (this of course is brimming with issues.) I will replace sites modified by this bug with ArraysSupport.newLength. Cheers, -- Jim > On Jun 2, 2020, at 7:08 PM, Stuart Marks <stuart.ma...@oracle.com> wrote: > > Hi Jim, > > This was mentioned previously in this thread but not discussed very much. I > suggest you take a look at jdk.internal.util.ArraysSupport.newLength(). Ivan > Gerasimov and I worked this over fairly closely last year, and I'm pretty > sure it does what Martin is saying, which I also think is the right thing. > > The intent is that it be used for things that have growable arrays, where the > array might have a larger capacity than the logical number of elements > currently stored. Sometimes the array needs to be grown to accommodate an > immediate need (minGrowth) but it's desired to grow larger (prefGrowth) in > anticipation of future needs. If minGrowth can't be accommodated, it throws > OOME, but if prefGrowth can't be accommodated, it might be acceptable to > provide a smaller amount of growth. > > (Of course, all this assumes that there is sufficient memory available to > allocate the actual array. ArraysSupport.newLength doesn't attempt to > ascertain that.) > > One issue is integer wraparound (overflow). This is the primary value that > ArraysSupport.newLength provides. It would be good to centralize these > computations instead of having them be spread all over. > > Another issue is the one that MAX_ARRAY_LENGTH (also called MAX_ARRAY_SIZE) > is trying to address. This is sort-of a misnomer. It's not the actual maximum > array size (which in fact isn't known the the library). It's actually the > maximum array size that the library is fairly confident the VM can provide, > assuming that enough memory is actually available. What the heck does that > mean? > > The theoretical maximum array size is Integer.MAX_VALUE, since the JLS and > JVMS don't allow anything larger. However, actual VM implementations will > refuse to allocate an array above a certain amount slightly smaller than > that, even if there is enough memory available. In practice, I believe the > values for current Hotspot are Integer.MAX_VALUE-3 or Integer.MAX_VALUE-2, > depending on whether compressed OOPS are in use. > > Why is this significant? Consider the following case, where the capacity of > something is currently Integer.MAX_VALUE-100, and it's filled with elements. > The application performs some operation that requires 50 elements (minGrowth) > be added. A new array could certainly be allocated with size > Integer.MAX_VALUE-50, but typical growth policies for these kinds of > containers want to increase the current capacity by 1.5x or 2x (prefGrowth). > Doing this multiplication would exceed Integer.MAX_VALUE, so that won't work. > Clearly, we need to clamp the capacity somewhere. > > We don't want to clamp the capacity at Integer.MAX_VALUE, because this > allocation would fail on every JVM I'm aware of, even if enough memory is > available. So we don't do that. Instead, we clamp the preferred growth at > some fairly arbitrary value smaller than Integer.MAX_VALUE, which is here > called MAX_ARRAY_LENGTH, and increase the capacity to that instead. This > allows the container's requested allocation to proceed: it satisfies > minGrowth, but it doesn't satisfy prefGrowth. Instead, it returns a capacity > value that's reasonably likely to succeed, given an unknown JVM > implementation limit. > > Recall that the container now has Integer.MAX_VALUE-50 elements and the > capacity is MAX_ARRAY_SIZE, which is currently defined somewhat arbitrarily > at Integer.MAX_VALUE-8. Suppose the application now wants to add 43 elements. > What should happen? > > We could say, this exceeds MAX_ARRAY_LENGTH, so refuse the request and throw > OOME. This is reasonable and consistent in some sense, but perhaps not in > another. Suppose there is sufficient memory, and the JVM does allow arrays of > Integer.MAX_VALUE-7 to be created. Shouldn't we even try? > > That's what hugeLength() does -- it returns a value that attempts an > allocation beyond the max preferential growth, and leaves it up to the JVM to > grant or refuse the request based on its own implementation limits. > > Anyway, this is all quite subtle, and maybe comments in ArraysSupport don't > describe this adequately. But the code that implements this kind of policy > has been copied to different locations around the JDK, and it uses somewhat > different terminology, and it might have slightly different bugs, but they're > all essentially trying to implement this policy. > > ** > > Several questions could be asked: > > 1) Is this the right policy for implementing growable arrays? > > 2) In cases where a class needs a growable array, can and should it be > refactored to use ArraysSupport.newLength()? > > 3) Does ArraysSupport.newLength() need to be modified to accommodate needs of > additional call sites? > > 4) We might want to consider refactoring PriorityBlockingQueue and ArrayDeque > to use ArraysSupport.newLength, in order to provide a consistent policy for > collections. Other growable-array-based collections (Vector, ArrayList, > PriorityQueue) do already. > > s'marks > > > > > > On 6/1/20 4:47 AM, Jim Laskey wrote: >> Thanks David will run with that, >>> On May 31, 2020, at 8:34 PM, David Holmes <david.hol...@oracle.com> wrote: >>> >>> On 31/05/2020 12:29 am, Jim Laskey wrote: >>>> I'm working through https://bugs.openjdk.java.net/browse/JDK-8230744 >>>> <https://bugs.openjdk.java.net/browse/JDK-8230744> Several classes throw >>>> OutOfMemoryError without message . >>>> I'm wondering why hugeCapacity in >>>> src/jdk.zipfs/share/classes/jdk/nio/zipfs/ByteArrayChannel.java is defined >>>> as >>>> /** >>>> * The maximum size of array to allocate. >>>> * Some VMs reserve some header words in an array. >>>> * Attempts to allocate larger arrays may result in >>>> * OutOfMemoryError: Requested array size exceeds VM limit >>>> */ >>>> private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; >>>> /** >>>> * Increases the capacity to ensure that it can hold at least the >>>> * number of elements specified by the minimum capacity argument. >>>> * >>>> * @param minCapacity the desired minimum capacity >>>> */ >>>> private void grow(int minCapacity) { >>>> // overflow-conscious code >>>> int oldCapacity = buf.length; >>>> int newCapacity = oldCapacity << 1; >>>> if (newCapacity - minCapacity < 0) >>>> newCapacity = minCapacity; >>>> if (newCapacity - MAX_ARRAY_SIZE > 0) >>>> newCapacity = hugeCapacity(minCapacity); >>>> buf = Arrays.copyOf(buf, newCapacity); >>>> } >>>> private static int hugeCapacity(int minCapacity) { >>>> if (minCapacity < 0) // overflow >>>> throw new OutOfMemoryError(); >>> >>> Not sure how we could have minCapacity < 0 at this point. It should have >>> been checked before the call to grow, and grow will not make it negative. >>> >>>> return (minCapacity > MAX_ARRAY_SIZE) ? >>>> Integer.MAX_VALUE : >>>> MAX_ARRAY_SIZE; >>> >>> That's a bug plain and simple. It should never report a size > >>> MAX_ARRAY_SIZE. >>> >>>> } >>>> It just seems that it's pushing the inevitable off to Arrays.copyOf. >>>> Shouldn't it be: >>>> private static int hugeCapacity(int minCapacity) { >>>> if (minCapacity < 0 || minCapacity > MAX_ARRAY_SIZE) { >>>> throw >>>> new OutOfMemoryError("ByteArrayChannel exceeds maximum >>>> size: " + >>>> MAX_ARRAY_SIZE); >>>> } >>>> return MAX_ARRAY_SIZE; >>>> } >>> >>> That seems more appropriate to me - modulo the question mark over >>> minCapacity being negative. >>> >>>> Real question: is there some hidden purpose behind this kind of logic? >>> >>> The basic strategy is to double the current capacity unless that will >>> trigger an unnecessary exception, in which case just use the requested >>> capacity, but again watch for the implementation limits. >>> >>> Cheers, >>> David >>> ----- >>> >>>> Cheers, >>>> -- Jim