Skip to content

Commit ece26c3

Browse files
author
Alaric McGregor
committed
Backport e5ce5c57c83972ff52758a804c942986cab74ca7 from JDK 26
ByteBuffer.allocateDirect initialization can result in large TTSP spikes This backport adapts the JDK 26 improvement for ByteBuffer.allocateDirect initialization to reduce TTSP spikes. Minor conflict in template resolved manually to preserve 2023 copyright.
1 parent 3cc0eca commit ece26c3

File tree

4 files changed

+157
-26
lines changed

4 files changed

+157
-26
lines changed

src/java.base/share/classes/java/nio/Bits.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -234,4 +234,28 @@ public long getMemoryUsed() {
234234
// of an element by element copy. These numbers may change over time.
235235
static final int JNI_COPY_TO_ARRAY_THRESHOLD = 6;
236236
static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6;
237+
238+
// Maximum number of bytes to set in one call to {@code Unsafe.setMemory}.
239+
// This threshold allows safepoint polling during large memory operations.
240+
static final long UNSAFE_SET_THRESHOLD = 1024 * 1024;
241+
242+
/**
243+
* Sets a block of memory starting from a given address to a specified byte value.
244+
*
245+
* @param srcAddr
246+
* the starting memory address
247+
* @param count
248+
* the number of bytes to set
249+
* @param value
250+
* the byte value to set
251+
*/
252+
static void setMemory(long srcAddr, long count, byte value) {
253+
long offset = 0;
254+
while (offset < count) {
255+
long len = Math.min(UNSAFE_SET_THRESHOLD, count - offset);
256+
UNSAFE.setMemory(srcAddr + offset, len, value);
257+
offset += len;
258+
}
259+
}
260+
237261
}

src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -31,7 +31,10 @@ import java.io.FileDescriptor;
3131
import java.lang.foreign.MemorySegment;
3232
import java.lang.ref.Reference;
3333
import java.util.Objects;
34+
import jdk.internal.foreign.AbstractMemorySegmentImpl;
3435
import jdk.internal.foreign.MemorySessionImpl;
36+
import jdk.internal.foreign.SegmentFactories;
37+
import jdk.internal.vm.annotation.ForceInline;
3538
import jdk.internal.misc.ScopedMemoryAccess.ScopedAccessError;
3639
import jdk.internal.misc.VM;
3740
import jdk.internal.ref.Cleaner;
@@ -56,14 +59,6 @@ class Direct$Type$Buffer$RW$$BO$
5659
{
5760

5861
#if[rw]
59-
60-
// Cached unaligned-access capability
61-
protected static final boolean UNALIGNED = Bits.unaligned();
62-
63-
// Base address, used in all indexing calculations
64-
// NOTE: moved up to Buffer.java for speed in JNI GetDirectBufferAddress
65-
// protected long address;
66-
6762
// An object attached to this buffer. If this buffer is a view of another
6863
// buffer then we use this field to keep a reference to that buffer to
6964
// ensure that its memory isn't freed before we are done with it.
@@ -74,6 +69,8 @@ class Direct$Type$Buffer$RW$$BO$
7469
}
7570

7671
#if[byte]
72+
// Cached unaligned-access capability
73+
static final boolean UNALIGNED = Bits.unaligned();
7774

7875
private record Deallocator(long address, long size, int capacity) implements Runnable {
7976
private Deallocator {
@@ -117,7 +114,7 @@ class Direct$Type$Buffer$RW$$BO$
117114
Bits.unreserveMemory(size, cap);
118115
throw x;
119116
}
120-
UNSAFE.setMemory(base, size, (byte) 0);
117+
Bits.setMemory(base, size, (byte) 0);
121118
if (pa && (base % ps != 0)) {
122119
// Round up to page boundary
123120
address = base + ps - (base & (ps - 1));
@@ -162,9 +159,10 @@ class Direct$Type$Buffer$RW$$BO$
162159
}
163160

164161
// Invoked only by JNI: NewDirectByteBuffer(void*, long)
162+
// and JavaNioAccess.newDirectByteBuffer(int).
165163
// The long-valued capacity is restricted to int range.
166164
//
167-
private Direct$Type$Buffer(long addr, long cap) {
165+
Direct$Type$Buffer(long addr, long cap) {
168166
super(-1, 0, checkCapacity(cap), (int)cap, null);
169167
address = addr;
170168
cleaner = null;
@@ -534,6 +532,24 @@ class Direct$Type$Buffer$RW$$BO$
534532
}
535533
#end[char]
536534

535+
#if[byte]
536+
@ForceInline
537+
@Override
538+
int scaleShifts() {
539+
return 0;
540+
}
541+
542+
@ForceInline
543+
@Override
544+
AbstractMemorySegmentImpl heapSegment(Object base,
545+
long offset,
546+
long length,
547+
boolean readOnly,
548+
MemorySessionImpl bufferScope) {
549+
// Direct buffers are not backed by an array.
550+
throw new UnsupportedOperationException();
551+
}
552+
#end[byte]
537553

538554
#if[byte]
539555
// #BIN

test/jdk/java/nio/Buffer/AllocateDirectInit.java

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2001, 2007, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -23,29 +23,60 @@
2323

2424
/**
2525
* @test
26-
* @bug 4490253 6535542
26+
* @bug 4490253 6535542 8357959
27+
* @key randomness
28+
* @library /test/lib
29+
* @build jdk.test.lib.RandomFactory
2730
* @summary Verify that newly allocated direct buffers are initialized.
31+
* @run main/othervm AllocateDirectInit
2832
*/
2933

3034
import java.nio.ByteBuffer;
35+
import java.util.Random;
36+
37+
import jdk.test.lib.RandomFactory;
3138

3239
public class AllocateDirectInit {
40+
private static final int MAX_BIN_LIMIT = 16 * 1024 * 1024;
41+
private static final int MAX_DEC_LIMIT = 10 * 1000 * 1000;
42+
private static final int TRIES_PER_LIMIT = 1024;
43+
44+
private static final Random RND = RandomFactory.getRandom();
45+
3346
public static void main(String [] args){
34-
for (int i = 0; i < 1024; i++) {
35-
ByteBuffer bb = ByteBuffer.allocateDirect(1024);
36-
// printByteBuffer(bb);
37-
for (bb.position(0); bb.position() < bb.limit(); ) {
38-
if ((bb.get() & 0xff) != 0)
39-
throw new RuntimeException("uninitialized buffer, position = "
40-
+ bb.position());
47+
// Try power of two limits
48+
for (int limit = 1; limit < MAX_BIN_LIMIT; limit *= 2) {
49+
check(ByteBuffer.allocateDirect(limit - 1));
50+
check(ByteBuffer.allocateDirect(limit));
51+
check(ByteBuffer.allocateDirect(limit + 1));
52+
}
53+
54+
// Try power of ten limits
55+
for (int limit = 1; limit < MAX_DEC_LIMIT; limit *= 10) {
56+
check(ByteBuffer.allocateDirect(limit - 1));
57+
check(ByteBuffer.allocateDirect(limit));
58+
check(ByteBuffer.allocateDirect(limit + 1));
59+
}
60+
61+
// Try random sizes within power of two limits
62+
for (int limit = 1; limit < MAX_BIN_LIMIT; limit *= 2) {
63+
for (int t = 0; t < TRIES_PER_LIMIT; t++) {
64+
check(ByteBuffer.allocateDirect(RND.nextInt(limit)));
4165
}
4266
}
4367
}
4468

45-
private static void printByteBuffer(ByteBuffer bb) {
46-
System.out.print("byte [");
47-
for (bb.position(0); bb.position() < bb.limit(); )
48-
System.out.print(" " + Integer.toHexString(bb.get() & 0xff));
49-
System.out.println(" ]");
69+
private static void check(ByteBuffer bb) {
70+
while (bb.hasRemaining()) {
71+
if (bb.get() != 0) {
72+
int mismatchPos = bb.position();
73+
System.out.print("byte [");
74+
for (bb.position(0); bb.position() < bb.limit(); ) {
75+
System.out.print(" " + Integer.toHexString(bb.get() & 0xff));
76+
}
77+
System.out.println(" ]");
78+
throw new RuntimeException("uninitialized buffer, position = " + mismatchPos);
79+
}
80+
}
5081
}
5182
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright Amazon.com Inc. 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+
package org.openjdk.bench.java.nio;
25+
26+
27+
import java.nio.ByteBuffer;
28+
import java.util.concurrent.TimeUnit;
29+
import org.openjdk.jmh.annotations.Benchmark;
30+
import org.openjdk.jmh.annotations.BenchmarkMode;
31+
import org.openjdk.jmh.annotations.Fork;
32+
import org.openjdk.jmh.annotations.Measurement;
33+
import org.openjdk.jmh.annotations.Mode;
34+
import org.openjdk.jmh.annotations.OutputTimeUnit;
35+
import org.openjdk.jmh.annotations.Param;
36+
import org.openjdk.jmh.annotations.Scope;
37+
import org.openjdk.jmh.annotations.State;
38+
import org.openjdk.jmh.annotations.Warmup;
39+
40+
@BenchmarkMode(Mode.AverageTime)
41+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
42+
@State(Scope.Thread)
43+
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
44+
@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
45+
@Fork(3)
46+
public class DirectByteBufferAlloc {
47+
48+
@Param({
49+
"128", // 128 bytes
50+
"1024", // 1KB
51+
"1048576", // 1 MB
52+
"16777216" // 16MB
53+
})
54+
public int bytes;
55+
56+
@Benchmark
57+
public ByteBuffer allocateDirectBuffer() {
58+
return ByteBuffer.allocateDirect(bytes);
59+
}
60+
}

0 commit comments

Comments
 (0)