#6961 use codegen to invoke the appropriate ByteBuffer Cleaner depending on JVM version

Fixes #8568
This commit is contained in:
Armin 2017-11-02 10:47:34 +01:00 committed by Armin Braun
parent f03f1b7775
commit 753cd9e104
4 changed files with 102 additions and 12 deletions

View file

@ -0,0 +1,72 @@
package org.logstash;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import org.codehaus.janino.ClassBodyEvaluator;
import org.logstash.ackedqueue.io.ByteBufferCleaner;
/**
* Logic around ensuring compatibility with Java 8 and 9 simultaneously.
*/
public final class LogstashJavaCompat {
/**
* True if current JVM is a Java 9 implementation.
*/
public static final boolean IS_JAVA_9_OR_GREATER = isAtLeastJava9();
/**
* Sets up an appropriate implementation of {@link ByteBufferCleaner} depending no whether or
* not the current JVM is a Java 9 implementation.
* @return ByteBufferCleaner
*/
public static ByteBufferCleaner setupBytebufferCleaner() {
final ClassBodyEvaluator se = new ClassBodyEvaluator();
final Collection<String> imports = new ArrayList<>();
imports.add("java.nio.MappedByteBuffer");
final String cleanerCode;
final String ctorCode;
final String fieldsCode;
if (isAtLeastJava9()) {
imports.add("sun.misc.Unsafe");
imports.add("java.lang.reflect.Field");
cleanerCode = "unsafe.invokeCleaner(buffer);";
ctorCode = "Field unsafeField = Unsafe.class.getDeclaredField(\"theUnsafe\");" +
"unsafeField.setAccessible(true);" +
"unsafe = (Unsafe) unsafeField.get(null);";
fieldsCode = "private final Unsafe unsafe;";
} else {
imports.add("sun.misc.Cleaner");
imports.add("sun.nio.ch.DirectBuffer");
cleanerCode = "Cleaner c=((DirectBuffer)buffer).cleaner();if(c != null){c.clean();}";
ctorCode = "";
fieldsCode = "";
}
se.setImplementedInterfaces(new Class[]{ByteBufferCleaner.class});
se.setClassName("ByteBufferCleanerImpl");
se.setDefaultImports(imports.toArray(new String[0]));
try {
return (ByteBufferCleaner) se.createInstance(
new StringReader(String.format(
"%s public ByteBufferCleanerImpl() throws Exception{%s} public void clean(MappedByteBuffer buffer){%s}",
fieldsCode, ctorCode, cleanerCode
))
);
} catch (final Exception ex) {
throw new IllegalStateException(ex);
}
}
/**
* Identifies whether we're running on Java 9 by parsing the first component of the
* {@code "java.version"} system property. For Java 9 this value is assumed to start with a
* {@code "9"}.
* @return True iff running on Java 9
*/
private static boolean isAtLeastJava9() {
final String version = System.getProperty("java.version");
final int end = version.indexOf('.');
return Integer.parseInt(version.substring(0, end > 0 ? end : version.length())) >= 9;
}
}

View file

@ -0,0 +1,16 @@
package org.logstash.ackedqueue.io;
import java.nio.MappedByteBuffer;
/**
* Function that forces garbage collection of a {@link MappedByteBuffer}.
*/
@FunctionalInterface
public interface ByteBufferCleaner {
/**
* Forces garbage collection of given buffer.
* @param buffer ByteBuffer to GC
*/
void clean(MappedByteBuffer buffer);
}

View file

@ -1,8 +1,5 @@
package org.logstash.ackedqueue.io;
import sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
@ -10,12 +7,16 @@ import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.logstash.LogstashJavaCompat;
// TODO: this essentially a copy of ByteBufferPageIO and should be DRY'ed - temp impl to test file based stress test
@SuppressWarnings("sunapi")
public class MmapPageIO extends AbstractByteBufferPageIO {
/**
* Cleaner function for forcing unmapping of backing {@link MmapPageIO#buffer}.
*/
private static final ByteBufferCleaner BUFFER_CLEANER =
LogstashJavaCompat.setupBytebufferCleaner();
private File file;
private FileChannel channel;
@ -108,11 +109,7 @@ public class MmapPageIO extends AbstractByteBufferPageIO {
public void close() throws IOException {
if (this.buffer != null) {
this.buffer.force();
// calling the cleaner() method releases resources held by this direct buffer which would be held until GC otherwise.
// see https://github.com/elastic/logstash/pull/6740
Cleaner cleaner = ((DirectBuffer) this.buffer).cleaner();
if (cleaner != null) { cleaner.clean(); }
BUFFER_CLEANER.clean(buffer);
}
if (this.channel != null) {

View file

@ -1,6 +1,7 @@
package org.logstash.instruments.monitors;
import org.junit.Test;
import org.logstash.LogstashJavaCompat;
import org.logstash.instrument.monitors.MemoryMonitor;
import java.util.Map;
@ -17,7 +18,11 @@ public class MemoryMonitorTest {
public void testEachHeapSpaceRepresented() {
Map<String, Map<String, Object>> heap = MemoryMonitor.detect(MemoryMonitor.Type.All).getHeap();
assertThat(heap, notNullValue());
assertThat(heap.keySet(), hasItems("PS Survivor Space", "PS Old Gen", "PS Eden Space"));
if (LogstashJavaCompat.IS_JAVA_9_OR_GREATER) {
assertThat(heap.keySet(), hasItems("G1 Old Gen", "G1 Survivor Space", "G1 Eden Space"));
} else {
assertThat(heap.keySet(), hasItems("PS Survivor Space", "PS Old Gen", "PS Eden Space"));
}
}
@Test