/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.core.io.nio; import java.io.File; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.util.Arrays; import io.netty.util.internal.PlatformDependent; import org.apache.activemq.artemis.ArtemisConstants; import org.apache.activemq.artemis.core.io.AbstractSequentialFileFactory; import org.apache.activemq.artemis.core.io.IOCriticalErrorListener; import org.apache.activemq.artemis.core.io.SequentialFile; import org.apache.activemq.artemis.utils.Env; public final class NIOSequentialFileFactory extends AbstractSequentialFileFactory { private static final int DEFAULT_CAPACITY_ALIGNMENT = Env.osPageSize(); private boolean bufferPooling; //pools only the biggest one -> optimized for the common case private final ThreadLocal<ByteBuffer> bytesPool; public NIOSequentialFileFactory(final File journalDir, final int maxIO) { this(journalDir, null, maxIO); } public NIOSequentialFileFactory(final File journalDir, final IOCriticalErrorListener listener, final int maxIO) { this(journalDir, false, ArtemisConstants.DEFAULT_JOURNAL_BUFFER_SIZE_NIO, ArtemisConstants.DEFAULT_JOURNAL_BUFFER_TIMEOUT_NIO, maxIO, false, listener); } public NIOSequentialFileFactory(final File journalDir, final boolean buffered, final int maxIO) { this(journalDir, buffered, null, maxIO); } public NIOSequentialFileFactory(final File journalDir, final boolean buffered, final IOCriticalErrorListener listener, final int maxIO) { this(journalDir, buffered, ArtemisConstants.DEFAULT_JOURNAL_BUFFER_SIZE_NIO, ArtemisConstants.DEFAULT_JOURNAL_BUFFER_TIMEOUT_NIO, maxIO, false, listener); } public NIOSequentialFileFactory(final File journalDir, final boolean buffered, final int bufferSize, final int bufferTimeout, final int maxIO, final boolean logRates) { this(journalDir, buffered, bufferSize, bufferTimeout, maxIO, logRates, null); } public NIOSequentialFileFactory(final File journalDir, final boolean buffered, final int bufferSize, final int bufferTimeout, final int maxIO, final boolean logRates, final IOCriticalErrorListener listener) { super(journalDir, buffered, bufferSize, bufferTimeout, maxIO, logRates, listener); this.bufferPooling = true; this.bytesPool = new ThreadLocal<>(); } public static ByteBuffer allocateDirectByteBuffer(final int size) { // Using direct buffer, as described on https://jira.jboss.org/browse/HORNETQ-467 ByteBuffer buffer2 = null; try { buffer2 = ByteBuffer.allocateDirect(size); } catch (OutOfMemoryError error) { // This is a workaround for the way the JDK will deal with native buffers. // the main portion is outside of the VM heap // and the JDK will not have any reference about it to take GC into account // so we force a GC and try again. WeakReference<Object> obj = new WeakReference<>(new Object()); try { long timeout = System.currentTimeMillis() + 5000; while (System.currentTimeMillis() > timeout && obj.get() != null) { System.gc(); Thread.sleep(100); } } catch (InterruptedException e) { } buffer2 = ByteBuffer.allocateDirect(size); } return buffer2; } public void enableBufferReuse() { this.bufferPooling = true; } public void disableBufferReuse() { this.bufferPooling = false; } @Override public SequentialFile createSequentialFile(final String fileName) { return new NIOSequentialFile(this, journalDir, fileName, maxIO, writeExecutor); } @Override public boolean isSupportsCallbacks() { return timedBuffer != null; } private static int align(final int value, final int pow2alignment) { return (value + (pow2alignment - 1)) & ~(pow2alignment - 1); } @Override public ByteBuffer allocateDirectBuffer(final int size) { final int requiredCapacity = align(size, DEFAULT_CAPACITY_ALIGNMENT); final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(requiredCapacity); byteBuffer.limit(size); return byteBuffer; } @Override public void releaseDirectBuffer(ByteBuffer buffer) { PlatformDependent.freeDirectBuffer(buffer); } @Override public ByteBuffer newBuffer(final int size) { if (!this.bufferPooling) { return allocateDirectBuffer(size); } else { final int requiredCapacity = align(size, DEFAULT_CAPACITY_ALIGNMENT); ByteBuffer byteBuffer = bytesPool.get(); if (byteBuffer == null || requiredCapacity > byteBuffer.capacity()) { //do not free the old one (if any) until the new one will be released into the pool! byteBuffer = ByteBuffer.allocateDirect(requiredCapacity); } else { bytesPool.set(null); PlatformDependent.setMemory(PlatformDependent.directBufferAddress(byteBuffer), size, (byte) 0); byteBuffer.clear(); } byteBuffer.limit(size); return byteBuffer; } } @Override public void releaseBuffer(ByteBuffer buffer) { if (this.bufferPooling) { if (buffer.isDirect()) { final ByteBuffer byteBuffer = bytesPool.get(); if (byteBuffer != buffer) { //replace with the current pooled only if greater or null if (byteBuffer == null || buffer.capacity() > byteBuffer.capacity()) { if (byteBuffer != null) { //free the smaller one PlatformDependent.freeDirectBuffer(byteBuffer); } bytesPool.set(buffer); } else { PlatformDependent.freeDirectBuffer(buffer); } } } } } @Override public void clearBuffer(final ByteBuffer buffer) { if (buffer.isDirect()) { PlatformDependent.setMemory(PlatformDependent.directBufferAddress(buffer), buffer.limit(), (byte) 0); } else { Arrays.fill(buffer.array(), buffer.arrayOffset(), buffer.limit(), (byte) 0); } } @Override public ByteBuffer wrapBuffer(final byte[] bytes) { return ByteBuffer.wrap(bytes); } @Override public int getAlignment() { return 1; } @Override public int calculateBlockSize(final int bytes) { return bytes; } }