mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8344332: (bf) Migrate DirectByteBuffer away from jdk.internal.ref.Cleaner
Reviewed-by: rriggs, bchristi
This commit is contained in:
parent
854de8c9c6
commit
21f2e9a71c
@ -101,8 +101,15 @@ class Bits { // package-private
|
||||
// increasing delay before throwing OutOfMemoryError:
|
||||
// 1, 2, 4, 8, 16, 32, 64, 128, 256 (total 511 ms ~ 0.5 s)
|
||||
// which means that OOME will be thrown after 0.5 s of trying
|
||||
private static final long INITIAL_SLEEP = 1;
|
||||
private static final int MAX_SLEEPS = 9;
|
||||
|
||||
private static final Object RESERVE_SLOWPATH_LOCK = new Object();
|
||||
|
||||
// Token for detecting whether some other thread has done a GC since the
|
||||
// last time the checking thread went around the retry-with-GC loop.
|
||||
private static int RESERVE_GC_EPOCH = 0; // Never negative.
|
||||
|
||||
// These methods should be called whenever direct memory is allocated or
|
||||
// freed. They allow the user to control the amount of direct memory
|
||||
// which a process may access. All sizes are specified in bytes.
|
||||
@ -118,29 +125,45 @@ class Bits { // package-private
|
||||
return;
|
||||
}
|
||||
|
||||
final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
|
||||
// Don't completely discard interruptions. Instead, record them and
|
||||
// reapply when we're done here (whether successfully or OOME).
|
||||
boolean interrupted = false;
|
||||
try {
|
||||
|
||||
// Retry allocation until success or there are no more
|
||||
// references (including Cleaners that might free direct
|
||||
// buffer memory) to process and allocation still fails.
|
||||
boolean refprocActive;
|
||||
do {
|
||||
// Keep trying to reserve until either succeed or there is no
|
||||
// further cleaning available from prior GCs. If the latter then
|
||||
// GC to hopefully find more cleaning to do. Once a thread GCs it
|
||||
// drops to the later retry with backoff loop.
|
||||
for (int cleanedEpoch = -1; true; ) {
|
||||
synchronized (RESERVE_SLOWPATH_LOCK) {
|
||||
// Test if cleaning for prior GCs (from here) is complete.
|
||||
// If so, GC to produce more cleaning work, and change
|
||||
// the token to inform other threads that there may be
|
||||
// more cleaning work to do. This is done under the lock
|
||||
// to close a race. We could have multiple threads pass
|
||||
// the test "simultaneously", resulting in back-to-back
|
||||
// GCs. For a STW GC the window is small, but for a
|
||||
// concurrent GC it's quite large. If a thread were to
|
||||
// somehow be stuck trying to take the lock while enough
|
||||
// other threads succeeded for the epoch to wrap, it just
|
||||
// does an excess GC.
|
||||
if (RESERVE_GC_EPOCH == cleanedEpoch) {
|
||||
// Increment with overflow to 0, so the value can
|
||||
// never equal the initial/reset cleanedEpoch value.
|
||||
RESERVE_GC_EPOCH = Integer.max(0, RESERVE_GC_EPOCH + 1);
|
||||
System.gc();
|
||||
break;
|
||||
}
|
||||
cleanedEpoch = RESERVE_GC_EPOCH;
|
||||
}
|
||||
try {
|
||||
refprocActive = jlra.waitForReferenceProcessing();
|
||||
if (tryReserveOrClean(size, cap)) {
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Defer interrupts and keep trying.
|
||||
interrupted = true;
|
||||
refprocActive = true;
|
||||
cleanedEpoch = -1; // Reset when incomplete.
|
||||
}
|
||||
if (tryReserveMemory(size, cap)) {
|
||||
return;
|
||||
}
|
||||
} while (refprocActive);
|
||||
|
||||
// trigger VM's Reference processing
|
||||
System.gc();
|
||||
}
|
||||
|
||||
// A retry loop with exponential back-off delays.
|
||||
// Sometimes it would suffice to give up once reference
|
||||
@ -151,40 +174,53 @@ class Bits { // package-private
|
||||
// DirectBufferAllocTest to (usually) succeed, while
|
||||
// without it that test likely fails. Since failure here
|
||||
// ends in OOME, there's no need to hurry.
|
||||
long sleepTime = 1;
|
||||
int sleeps = 0;
|
||||
while (true) {
|
||||
if (tryReserveMemory(size, cap)) {
|
||||
return;
|
||||
}
|
||||
if (sleeps >= MAX_SLEEPS) {
|
||||
break;
|
||||
}
|
||||
for (int sleeps = 0; true; ) {
|
||||
try {
|
||||
if (!jlra.waitForReferenceProcessing()) {
|
||||
Thread.sleep(sleepTime);
|
||||
sleepTime <<= 1;
|
||||
sleeps++;
|
||||
if (tryReserveOrClean(size, cap)) {
|
||||
return;
|
||||
} else if (sleeps < MAX_SLEEPS) {
|
||||
Thread.sleep(INITIAL_SLEEP << sleeps);
|
||||
++sleeps; // Only increment if sleep completed.
|
||||
} else {
|
||||
throw new OutOfMemoryError
|
||||
("Cannot reserve "
|
||||
+ size + " bytes of direct buffer memory (allocated: "
|
||||
+ RESERVED_MEMORY.get() + ", limit: " + MAX_MEMORY +")");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// no luck
|
||||
throw new OutOfMemoryError
|
||||
("Cannot reserve "
|
||||
+ size + " bytes of direct buffer memory (allocated: "
|
||||
+ RESERVED_MEMORY.get() + ", limit: " + MAX_MEMORY +")");
|
||||
|
||||
} finally {
|
||||
// Reapply any deferred interruption.
|
||||
if (interrupted) {
|
||||
// don't swallow interrupts
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to reserve memory, or failing that, try to make progress on
|
||||
// cleaning. Returns true if successfully reserved memory, false if
|
||||
// failed and ran out of cleaning work.
|
||||
private static boolean tryReserveOrClean(long size, long cap)
|
||||
throws InterruptedException
|
||||
{
|
||||
JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
|
||||
boolean progressing = true;
|
||||
while (true) {
|
||||
if (tryReserveMemory(size, cap)) {
|
||||
return true;
|
||||
} else if (BufferCleaner.tryCleaning()) {
|
||||
progressing = true;
|
||||
} else if (!progressing) {
|
||||
return false;
|
||||
} else {
|
||||
progressing = jlra.waitForReferenceProcessing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean tryReserveMemory(long size, long cap) {
|
||||
|
||||
// -XX:MaxDirectMemorySize limits the total capacity rather than the
|
||||
|
||||
269
src/java.base/share/classes/java/nio/BufferCleaner.java
Normal file
269
src/java.base/share/classes/java/nio/BufferCleaner.java
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.nio;
|
||||
|
||||
import java.lang.ref.PhantomReference;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.util.Objects;
|
||||
import sun.nio.Cleaner;
|
||||
|
||||
/**
|
||||
* BufferCleaner supports PhantomReference-based management of native memory
|
||||
* referred to by Direct-XXX-Buffers. Unreferenced DBBs may be garbage
|
||||
* collected, deactivating the associated PRefs and making them available for
|
||||
* cleanup here.
|
||||
*
|
||||
* There is a configured limit to the amount of memory that may be allocated
|
||||
* by DBBs. When that limit is reached, the allocator may invoke the garbage
|
||||
* collector directly to attempt to trigger cleaning here, hopefully
|
||||
* permitting the allocation to complete. Only if that doesn't free sufficient
|
||||
* memory does the allocation fail. See java.nio.Bits::reserveMemory() for
|
||||
* details.
|
||||
*
|
||||
* One of the requirements for that approach is having a way to determine that
|
||||
* deactivated cleaners have been cleaned. java.lang.ref.Cleaner doesn't
|
||||
* provide such a mechanism, and adding such a mechanism to that class to
|
||||
* satisfy this unique requirement was deemed undesirable. Instead, this class
|
||||
* uses the underlying primitives (PhantomReferences, ReferenceQueues) to
|
||||
* provide the functionality needed for DBB management.
|
||||
*/
|
||||
class BufferCleaner {
|
||||
private static final class PhantomCleaner
|
||||
extends PhantomReference<Object>
|
||||
implements Cleaner
|
||||
{
|
||||
private final Runnable action;
|
||||
// Position in the CleanerList.
|
||||
CleanerList.Node node;
|
||||
int index;
|
||||
|
||||
public PhantomCleaner(Object obj, Runnable action) {
|
||||
super(obj, queue);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clean() {
|
||||
if (cleanerList.remove(this)) {
|
||||
// If being cleaned explicitly by application, rather than via
|
||||
// reference processing by BufferCleaner, clear the referent so
|
||||
// reference processing is disabled for this object.
|
||||
clear();
|
||||
try {
|
||||
action.run();
|
||||
} catch (Throwable x) {
|
||||
// Long-standing behavior: when cleaning fails, VM exits.
|
||||
if (System.err != null) {
|
||||
new Error("nio Cleaner terminated abnormally", x).printStackTrace();
|
||||
}
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cribbed from jdk.internal.ref.CleanerImpl.
|
||||
static final class CleanerList {
|
||||
/**
|
||||
* Capacity for a single node in the list.
|
||||
* This balances memory overheads vs locality vs GC walking costs.
|
||||
*/
|
||||
static final int NODE_CAPACITY = 4096;
|
||||
|
||||
/**
|
||||
* Head node. This is the only node where PhantomCleanabls are
|
||||
* added to or removed from. This is the only node with variable size,
|
||||
* all other nodes linked from the head are always at full capacity.
|
||||
*/
|
||||
private Node head;
|
||||
|
||||
/**
|
||||
* Cached node instance to provide better behavior near NODE_CAPACITY
|
||||
* threshold: if list size flips around NODE_CAPACITY, it would reuse
|
||||
* the cached node instead of wasting and re-allocating a new node all
|
||||
* the time.
|
||||
*/
|
||||
private Node cache;
|
||||
|
||||
public CleanerList() {
|
||||
this.head = new Node();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert this PhantomCleaner in the list.
|
||||
*/
|
||||
public synchronized void insert(PhantomCleaner phc) {
|
||||
if (head.size == NODE_CAPACITY) {
|
||||
// Head node is full, insert new one.
|
||||
// If possible, pick a pre-allocated node from cache.
|
||||
Node newHead;
|
||||
if (cache != null) {
|
||||
newHead = cache;
|
||||
cache = null;
|
||||
} else {
|
||||
newHead = new Node();
|
||||
}
|
||||
newHead.next = head;
|
||||
head = newHead;
|
||||
}
|
||||
assert head.size < NODE_CAPACITY;
|
||||
|
||||
// Put the incoming object in head node and record indexes.
|
||||
final int lastIndex = head.size;
|
||||
phc.node = head;
|
||||
phc.index = lastIndex;
|
||||
head.arr[lastIndex] = phc;
|
||||
head.size++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this PhantomCleaner from the list.
|
||||
*
|
||||
* @return true if Cleaner was removed or false if not because
|
||||
* it had already been removed before
|
||||
*/
|
||||
public synchronized boolean remove(PhantomCleaner phc) {
|
||||
if (phc.node == null) {
|
||||
// Not in the list.
|
||||
return false;
|
||||
}
|
||||
assert phc.node.arr[phc.index] == phc;
|
||||
|
||||
// Replace with another element from the head node, as long
|
||||
// as it is not the same element. This keeps all non-head
|
||||
// nodes at full capacity.
|
||||
final int lastIndex = head.size - 1;
|
||||
assert lastIndex >= 0;
|
||||
if (head != phc.node || (phc.index != lastIndex)) {
|
||||
PhantomCleaner mover = head.arr[lastIndex];
|
||||
mover.node = phc.node;
|
||||
mover.index = phc.index;
|
||||
phc.node.arr[phc.index] = mover;
|
||||
}
|
||||
|
||||
// Now we can unlink the removed element.
|
||||
phc.node = null;
|
||||
|
||||
// Remove the last element from the head node.
|
||||
head.arr[lastIndex] = null;
|
||||
head.size--;
|
||||
|
||||
// If head node becomes empty after this, and there are
|
||||
// nodes that follow it, replace the head node with another
|
||||
// full one. If needed, stash the now free node in cache.
|
||||
if (head.size == 0 && head.next != null) {
|
||||
Node newHead = head.next;
|
||||
if (cache == null) {
|
||||
cache = head;
|
||||
cache.next = null;
|
||||
}
|
||||
head = newHead;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Segment node.
|
||||
*/
|
||||
static class Node {
|
||||
// Array of tracked cleaners, and the amount of elements in it.
|
||||
final PhantomCleaner[] arr = new PhantomCleaner[NODE_CAPACITY];
|
||||
int size;
|
||||
|
||||
// Linked list structure.
|
||||
Node next;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CleaningThread extends Thread {
|
||||
public CleaningThread() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
Cleaner c = (Cleaner) queue.remove();
|
||||
c.clean();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore InterruptedException in cleaner thread.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to do some cleaning. Takes a cleaner from the queue and executes it.
|
||||
*
|
||||
* @return true if a cleaner was found and executed, false if there
|
||||
* weren't any cleaners in the queue.
|
||||
*/
|
||||
public static boolean tryCleaning() {
|
||||
Cleaner c = (Cleaner) queue.poll();
|
||||
if (c == null) {
|
||||
return false;
|
||||
} else {
|
||||
c.clean();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static final CleanerList cleanerList = new CleanerList();
|
||||
private static final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
|
||||
private static CleaningThread cleaningThread = null;
|
||||
|
||||
private static void startCleaningThreadIfNeeded() {
|
||||
synchronized (cleanerList) {
|
||||
if (cleaningThread != null) {
|
||||
return;
|
||||
}
|
||||
cleaningThread = new CleaningThread();
|
||||
}
|
||||
cleaningThread.setDaemon(true);
|
||||
cleaningThread.start();
|
||||
}
|
||||
|
||||
private BufferCleaner() {}
|
||||
|
||||
/**
|
||||
* Construct a new Cleaner for obj, with the associated action.
|
||||
*
|
||||
* @param obj object to track.
|
||||
* @param action cleanup action for obj.
|
||||
* @return associated cleaner.
|
||||
*
|
||||
*/
|
||||
public static Cleaner register(Object obj, Runnable action) {
|
||||
Objects.requireNonNull(obj, "obj");
|
||||
Objects.requireNonNull(action, "action");
|
||||
startCleaningThreadIfNeeded();
|
||||
PhantomCleaner cleaner = new PhantomCleaner(obj, action);
|
||||
cleanerList.insert(cleaner);
|
||||
Reference.reachabilityFence(obj);
|
||||
return cleaner;
|
||||
}
|
||||
}
|
||||
@ -30,6 +30,7 @@ package java.nio;
|
||||
import java.io.FileDescriptor;
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.lang.ref.Reference;
|
||||
import java.nio.BufferCleaner;
|
||||
import java.util.Objects;
|
||||
import jdk.internal.foreign.AbstractMemorySegmentImpl;
|
||||
import jdk.internal.foreign.MemorySessionImpl;
|
||||
@ -37,7 +38,7 @@ import jdk.internal.foreign.SegmentFactories;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.misc.ScopedMemoryAccess.ScopedAccessError;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.ref.Cleaner;
|
||||
import sun.nio.Cleaner;
|
||||
import sun.nio.ch.DirectBuffer;
|
||||
|
||||
|
||||
@ -122,7 +123,7 @@ class Direct$Type$Buffer$RW$$BO$
|
||||
address = base;
|
||||
}
|
||||
try {
|
||||
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
|
||||
cleaner = BufferCleaner.register(this, new Deallocator(base, size, cap));
|
||||
} catch (Throwable t) {
|
||||
// Prevent leak if the Deallocator or Cleaner fail for any reason
|
||||
UNSAFE.freeMemory(base);
|
||||
@ -197,7 +198,7 @@ class Direct$Type$Buffer$RW$$BO$
|
||||
#if[rw]
|
||||
super(-1, 0, cap, cap, fd, isSync, segment);
|
||||
address = addr;
|
||||
cleaner = Cleaner.create(this, unmapper);
|
||||
cleaner = (unmapper == null) ? null : BufferCleaner.register(this, unmapper);
|
||||
att = null;
|
||||
#else[rw]
|
||||
super(cap, addr, fd, unmapper, isSync, segment);
|
||||
|
||||
@ -25,9 +25,9 @@
|
||||
|
||||
package jdk.internal.misc;
|
||||
|
||||
import jdk.internal.ref.Cleaner;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
||||
import sun.nio.Cleaner;
|
||||
import sun.nio.ch.DirectBuffer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
38
src/java.base/share/classes/sun/nio/Cleaner.java
Normal file
38
src/java.base/share/classes/sun/nio/Cleaner.java
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.nio;
|
||||
|
||||
/**
|
||||
* {@code Cleaner} represents an object and a cleaning action.
|
||||
*/
|
||||
public interface Cleaner {
|
||||
/**
|
||||
* Unregisters the cleaner and invokes the cleaning action.
|
||||
* The cleaner's cleaning action is invoked at most once,
|
||||
* regardless of the number of calls to {@code clean}.
|
||||
*/
|
||||
void clean();
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,7 +25,7 @@
|
||||
|
||||
package sun.nio.ch;
|
||||
|
||||
import jdk.internal.ref.Cleaner;
|
||||
import sun.nio.Cleaner;
|
||||
|
||||
|
||||
public interface DirectBuffer {
|
||||
|
||||
@ -58,11 +58,11 @@ import jdk.internal.misc.ExtendedMapMode;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.misc.VM.BufferPool;
|
||||
import jdk.internal.ref.Cleaner;
|
||||
import jdk.internal.ref.CleanerFactory;
|
||||
import jdk.internal.event.FileReadEvent;
|
||||
import jdk.internal.event.FileWriteEvent;
|
||||
import jdk.internal.access.foreign.UnmapperProxy;
|
||||
import sun.nio.Cleaner;
|
||||
|
||||
public class FileChannelImpl
|
||||
extends FileChannel
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package org.openjdk.bench.java.nio;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Thread)
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||
@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||
@Fork(value = 3, jvmArgs = {"-Xmx256m", "-Xms256m", "-XX:+AlwaysPreTouch"})
|
||||
public class DirectByteBufferChurn {
|
||||
|
||||
@Param({"128", "256", "512", "1024", "2048"})
|
||||
int recipFreq;
|
||||
|
||||
@Benchmark
|
||||
public Object test() {
|
||||
boolean direct = ThreadLocalRandom.current().nextInt(recipFreq) == 0;
|
||||
return direct ? ByteBuffer.allocateDirect(1) : ByteBuffer.allocate(1);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package org.openjdk.bench.java.nio;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||
@State(Scope.Thread)
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||
@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||
@Fork(value = 3, jvmArgs = {"-Xmx1g", "-Xms1g", "-XX:+AlwaysPreTouch"})
|
||||
public class DirectByteBufferGC {
|
||||
|
||||
@Param({"16384", "65536", "262144", "1048576", "4194304"})
|
||||
int count;
|
||||
|
||||
// Make sure all buffers are reachable and available for GC. Buffers
|
||||
// directly reference their Cleanables, so we do not want to provide
|
||||
// excess GC parallelism opportunities here, this is why reference
|
||||
// buffers from a linked list.
|
||||
//
|
||||
// This exposes the potential GC parallelism problem in Cleaner lists.
|
||||
LinkedList<ByteBuffer> buffers;
|
||||
|
||||
@Setup
|
||||
public void setup() {
|
||||
buffers = new LinkedList<>();
|
||||
for (int c = 0; c < count; c++) {
|
||||
buffers.add(ByteBuffer.allocateDirect(1));
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void test() {
|
||||
System.gc();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user