diff --git a/jme3-core/src/main/java/com/jme3/util/BufferAllocator.java b/jme3-core/src/main/java/com/jme3/util/BufferAllocator.java new file mode 100644 index 000000000..e7d429db6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/BufferAllocator.java @@ -0,0 +1,12 @@ +package com.jme3.util; + +import java.nio.Buffer; +import java.nio.ByteBuffer; + +public interface BufferAllocator { + + void destroyDirectBuffer(Buffer toBeDestroyed); + + ByteBuffer allocate(int size); + +} diff --git a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java index 20189afb1..6ad4712b8 100644 --- a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java +++ b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java @@ -31,17 +31,10 @@ */ package com.jme3.util; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.math.Vector4f; import java.io.UnsupportedEncodingException; import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -51,8 +44,12 @@ import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.ShortBuffer; import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import java.util.logging.Logger; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; /** * BufferUtils is a helper class for generating nio buffers from @@ -62,27 +59,52 @@ import java.util.logging.Logger; * @version $Id: BufferUtils.java,v 1.16 2007/10/29 16:56:18 nca Exp $ */ public final class BufferUtils { + private static BufferAllocator allocator = new PrimitiveAllocator(); private static boolean trackDirectMemory = false; private static ReferenceQueue removeCollected = new ReferenceQueue(); private static ConcurrentHashMap trackedBuffers = new ConcurrentHashMap(); static ClearReferences cleanupthread; + private static boolean used; + + static { + try { + allocator = new ReflectionAllocator(); + } catch (Throwable t) { + t.printStackTrace(); + System.err.println("Error using ReflectionAllocator"); + } + } + + /** + * Warning! do only set this before JME is started! + */ + public static void setAllocator(BufferAllocator allocator) { + if (used) { + throw new IllegalStateException( + "An Buffer was already allocated, since it is quite likely that other dispose methods will create native dangling pointers or other fun things, this is forbidden to be changed at runtime"); + } + BufferUtils.allocator = allocator; + } + /** - * Set it to true if you want to enable direct memory tracking for debugging purpose. - * Default is false. - * To print direct memory usage use BufferUtils.printCurrentDirectMemory(StringBuilder store); - * @param enabled + * Set it to true if you want to enable direct memory tracking for debugging + * purpose. Default is false. To print direct memory usage use + * BufferUtils.printCurrentDirectMemory(StringBuilder store); + * + * @param enabled */ public static void setTrackDirectMemoryEnabled(boolean enabled) { trackDirectMemory = enabled; } /** - * Creates a clone of the given buffer. The clone's capacity is - * equal to the given buffer's limit. + * Creates a clone of the given buffer. The clone's capacity is equal to the + * given buffer's limit. * - * @param buf The buffer to clone + * @param buf + * The buffer to clone * @return The cloned buffer */ public static Buffer clone(Buffer buf) { @@ -102,55 +124,31 @@ public final class BufferUtils { } private static void onBufferAllocated(Buffer buffer) { - /** - * StackTraceElement[] stackTrace = new Throwable().getStackTrace(); int - * initialIndex = 0; - * - * for (int i = 0; i < stackTrace.length; i++){ if - * (!stackTrace[i].getClassName().equals(BufferUtils.class.getName())){ - * initialIndex = i; break; } } - * - * int allocated = buffer.capacity(); int size = 0; - * - * if (buffer instanceof FloatBuffer){ size = 4; }else if (buffer - * instanceof ShortBuffer){ size = 2; }else if (buffer instanceof - * ByteBuffer){ size = 1; }else if (buffer instanceof IntBuffer){ size = - * 4; }else if (buffer instanceof DoubleBuffer){ size = 8; } - * - * allocated *= size; - * - * for (int i = initialIndex; i < stackTrace.length; i++){ - * StackTraceElement element = stackTrace[i]; if - * (element.getClassName().startsWith("java")){ break; } - * - * try { Class clazz = Class.forName(element.getClassName()); if (i == - * initialIndex){ - * System.out.println(clazz.getSimpleName()+"."+element.getMethodName - * ()+"():" + element.getLineNumber() + " allocated " + allocated); - * }else{ System.out.println(" at " + - * clazz.getSimpleName()+"."+element.getMethodName()+"()"); } } catch - * (ClassNotFoundException ex) { } } - */ + used = true; if (BufferUtils.trackDirectMemory) { - if (BufferUtils.cleanupthread == null) { BufferUtils.cleanupthread = new ClearReferences(); BufferUtils.cleanupthread.start(); } if (buffer instanceof ByteBuffer) { - BufferInfo info = new BufferInfo(ByteBuffer.class, buffer.capacity(), buffer, BufferUtils.removeCollected); + BufferInfo info = new BufferInfo(ByteBuffer.class, buffer.capacity(), buffer, + BufferUtils.removeCollected); BufferUtils.trackedBuffers.put(info, info); } else if (buffer instanceof FloatBuffer) { - BufferInfo info = new BufferInfo(FloatBuffer.class, buffer.capacity() * 4, buffer, BufferUtils.removeCollected); + BufferInfo info = new BufferInfo(FloatBuffer.class, buffer.capacity() * 4, buffer, + BufferUtils.removeCollected); BufferUtils.trackedBuffers.put(info, info); } else if (buffer instanceof IntBuffer) { - BufferInfo info = new BufferInfo(IntBuffer.class, buffer.capacity() * 4, buffer, BufferUtils.removeCollected); + BufferInfo info = new BufferInfo(IntBuffer.class, buffer.capacity() * 4, buffer, + BufferUtils.removeCollected); BufferUtils.trackedBuffers.put(info, info); } else if (buffer instanceof ShortBuffer) { - BufferInfo info = new BufferInfo(ShortBuffer.class, buffer.capacity() * 2, buffer, BufferUtils.removeCollected); + BufferInfo info = new BufferInfo(ShortBuffer.class, buffer.capacity() * 2, buffer, + BufferUtils.removeCollected); BufferUtils.trackedBuffers.put(info, info); } else if (buffer instanceof DoubleBuffer) { - BufferInfo info = new BufferInfo(DoubleBuffer.class, buffer.capacity() * 8, buffer, BufferUtils.removeCollected); + BufferInfo info = new BufferInfo(DoubleBuffer.class, buffer.capacity() * 8, buffer, + BufferUtils.removeCollected); BufferUtils.trackedBuffers.put(info, info); } @@ -158,11 +156,12 @@ public final class BufferUtils { } /** - * Generate a new FloatBuffer using the given array of Vector3f objects. - * The FloatBuffer will be 3 * data.length long and contain the vector data - * as data[0].x, data[0].y, data[0].z, data[1].x... etc. + * Generate a new FloatBuffer using the given array of Vector3f objects. The + * FloatBuffer will be 3 * data.length long and contain the vector data as + * data[0].x, data[0].y, data[0].z, data[1].x... etc. * - * @param data array of Vector3f objects to place into a new FloatBuffer + * @param data + * array of Vector3f objects to place into a new FloatBuffer */ public static FloatBuffer createFloatBuffer(Vector3f... data) { if (data == null) { @@ -184,7 +183,8 @@ public final class BufferUtils { * Generate a new FloatBuffer using the given array of Quaternion objects. * The FloatBuffer will be 4 * data.length long and contain the vector data. * - * @param data array of Quaternion objects to place into a new FloatBuffer + * @param data + * array of Quaternion objects to place into a new FloatBuffer */ public static FloatBuffer createFloatBuffer(Quaternion... data) { if (data == null) { @@ -203,10 +203,11 @@ public final class BufferUtils { } /** - * Generate a new FloatBuffer using the given array of Vector4 objects. - * The FloatBuffer will be 4 * data.length long and contain the vector data. + * Generate a new FloatBuffer using the given array of Vector4 objects. The + * FloatBuffer will be 4 * data.length long and contain the vector data. * - * @param data array of Vector4 objects to place into a new FloatBuffer + * @param data + * array of Vector4 objects to place into a new FloatBuffer */ public static FloatBuffer createFloatBuffer(Vector4f... data) { if (data == null) { @@ -228,7 +229,8 @@ public final class BufferUtils { * Generate a new FloatBuffer using the given array of ColorRGBA objects. * The FloatBuffer will be 4 * data.length long and contain the color data. * - * @param data array of ColorRGBA objects to place into a new FloatBuffer + * @param data + * array of ColorRGBA objects to place into a new FloatBuffer */ public static FloatBuffer createFloatBuffer(ColorRGBA... data) { if (data == null) { @@ -248,7 +250,9 @@ public final class BufferUtils { /** * Generate a new FloatBuffer using the given array of float primitives. - * @param data array of float primitives to place into a new FloatBuffer + * + * @param data + * array of float primitives to place into a new FloatBuffer */ public static FloatBuffer createFloatBuffer(float... data) { if (data == null) { @@ -307,8 +311,7 @@ public final class BufferUtils { * @param index * the postion to place the data; in terms of colors not floats */ - public static void setInBuffer(ColorRGBA color, FloatBuffer buf, - int index) { + public static void setInBuffer(ColorRGBA color, FloatBuffer buf, int index) { buf.position(index * 4); buf.put(color.r); buf.put(color.g); @@ -317,18 +320,18 @@ public final class BufferUtils { } /** - * Sets the data contained in the given quaternion into the FloatBuffer at the - * specified index. + * Sets the data contained in the given quaternion into the FloatBuffer at + * the specified index. * * @param quat * the {@link Quaternion} to insert * @param buf * the buffer to insert into * @param index - * the position to place the data; in terms of quaternions not floats + * the position to place the data; in terms of quaternions not + * floats */ - public static void setInBuffer(Quaternion quat, FloatBuffer buf, - int index) { + public static void setInBuffer(Quaternion quat, FloatBuffer buf, int index) { buf.position(index * 4); buf.put(quat.getX()); buf.put(quat.getY()); @@ -347,8 +350,7 @@ public final class BufferUtils { * @param index * the position to place the data; in terms of vector4 not floats */ - public static void setInBuffer(Vector4f vec, FloatBuffer buf, - int index) { + public static void setInBuffer(Vector4f vec, FloatBuffer buf, int index) { buf.position(index * 4); buf.put(vec.getX()); buf.put(vec.getY()); @@ -438,8 +440,8 @@ public final class BufferUtils { /** * Copies a Vector3f from one position in the buffer to another. The index - * values are in terms of vector number (eg, vector number 0 is positions 0-2 - * in the FloatBuffer.) + * values are in terms of vector number (eg, vector number 0 is positions + * 0-2 in the FloatBuffer.) * * @param buf * the buffer to copy from/to @@ -534,11 +536,12 @@ public final class BufferUtils { // // -- VECTOR2F METHODS -- //// /** - * Generate a new FloatBuffer using the given array of Vector2f objects. - * The FloatBuffer will be 2 * data.length long and contain the vector data - * as data[0].x, data[0].y, data[1].x... etc. + * Generate a new FloatBuffer using the given array of Vector2f objects. The + * FloatBuffer will be 2 * data.length long and contain the vector data as + * data[0].x, data[0].y, data[1].x... etc. * - * @param data array of Vector2f objects to place into a new FloatBuffer + * @param data + * array of Vector2f objects to place into a new FloatBuffer */ public static FloatBuffer createFloatBuffer(Vector2f... data) { if (data == null) { @@ -643,8 +646,8 @@ public final class BufferUtils { /** * Copies a Vector2f from one position in the buffer to another. The index - * values are in terms of vector number (eg, vector number 0 is positions 0-1 - * in the FloatBuffer.) + * values are in terms of vector number (eg, vector number 0 is positions + * 0-1 in the FloatBuffer.) * * @param buf * the buffer to copy from/to @@ -737,7 +740,7 @@ public final class BufferUtils { return eq; } - //// -- INT METHODS -- //// + //// -- INT METHODS -- //// /** * Generate a new IntBuffer using the given array of ints. The IntBuffer * will be data.length long and contain the int data as data[0], data[1]... @@ -806,7 +809,7 @@ public final class BufferUtils { * @return the new DoubleBuffer */ public static DoubleBuffer createDoubleBuffer(int size) { - DoubleBuffer buf = ByteBuffer.allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer(); + DoubleBuffer buf = allocator.allocate(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer(); buf.clear(); onBufferAllocated(buf); return buf; @@ -869,7 +872,7 @@ public final class BufferUtils { * @return the new FloatBuffer */ public static FloatBuffer createFloatBuffer(int size) { - FloatBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer(); + FloatBuffer buf = allocator.allocate(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer(); buf.clear(); onBufferAllocated(buf); return buf; @@ -931,7 +934,7 @@ public final class BufferUtils { * @return the new IntBuffer */ public static IntBuffer createIntBuffer(int size) { - IntBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer(); + IntBuffer buf = allocator.allocate(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer(); buf.clear(); onBufferAllocated(buf); return buf; @@ -994,7 +997,7 @@ public final class BufferUtils { * @return the new IntBuffer */ public static ByteBuffer createByteBuffer(int size) { - ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + ByteBuffer buf = allocator.allocate(size).order(ByteOrder.nativeOrder()); buf.clear(); onBufferAllocated(buf); return buf; @@ -1076,7 +1079,7 @@ public final class BufferUtils { * @return the new ShortBuffer */ public static ShortBuffer createShortBuffer(int size) { - ShortBuffer buf = ByteBuffer.allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer(); + ShortBuffer buf = allocator.allocate(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer(); buf.clear(); onBufferAllocated(buf); return buf; @@ -1115,9 +1118,9 @@ public final class BufferUtils { } /** - * Creates a new ShortBuffer with the same contents as the given ShortBuffer. - * The new ShortBuffer is separate from the old one and changes are not - * reflected across. If you want to reflect changes, consider using + * Creates a new ShortBuffer with the same contents as the given + * ShortBuffer. The new ShortBuffer is separate from the old one and changes + * are not reflected across. If you want to reflect changes, consider using * Buffer.duplicate(). * * @param buf @@ -1142,12 +1145,18 @@ public final class BufferUtils { } /** - * Ensures there is at least the required number of entries left after the current position of the - * buffer. If the buffer is too small a larger one is created and the old one copied to the new buffer. - * @param buffer buffer that should be checked/copied (may be null) - * @param required minimum number of elements that should be remaining in the returned buffer - * @return a buffer large enough to receive at least the required number of entries, same position as - * the input buffer, not null + * Ensures there is at least the required number of entries + * left after the current position of the buffer. If the buffer is too small + * a larger one is created and the old one copied to the new buffer. + * + * @param buffer + * buffer that should be checked/copied (may be null) + * @param required + * minimum number of elements that should be remaining in the + * returned buffer + * @return a buffer large enough to receive at least the + * required number of entries, same position as the + * input buffer, not null */ public static FloatBuffer ensureLargeEnough(FloatBuffer buffer, int required) { if (buffer != null) { @@ -1165,7 +1174,7 @@ public final class BufferUtils { } return buffer; } - + public static IntBuffer ensureLargeEnough(IntBuffer buffer, int required) { if (buffer != null) { buffer.limit(buffer.capacity()); @@ -1182,7 +1191,6 @@ public final class BufferUtils { } return buffer; } - public static ShortBuffer ensureLargeEnough(ShortBuffer buffer, int required) { if (buffer != null) { @@ -1255,108 +1263,46 @@ public final class BufferUtils { } store.append("Existing buffers: ").append(BufferUtils.trackedBuffers.size()).append("\n"); - store.append("(b: ").append(bBufs).append(" f: ").append(fBufs).append(" i: ").append(iBufs).append(" s: ").append(sBufs).append(" d: ").append(dBufs).append(")").append("\n"); + store.append("(b: ").append(bBufs).append(" f: ").append(fBufs).append(" i: ").append(iBufs) + .append(" s: ").append(sBufs).append(" d: ").append(dBufs).append(")").append("\n"); store.append("Total heap memory held: ").append(heapMem / 1024).append("kb\n"); store.append("Total direct memory held: ").append(totalHeld / 1024).append("kb\n"); - store.append("(b: ").append(bBufsM / 1024).append("kb f: ").append(fBufsM / 1024).append("kb i: ").append(iBufsM / 1024).append("kb s: ").append(sBufsM / 1024).append("kb d: ").append(dBufsM / 1024).append("kb)").append("\n"); + store.append("(b: ").append(bBufsM / 1024).append("kb f: ").append(fBufsM / 1024).append("kb i: ") + .append(iBufsM / 1024).append("kb s: ").append(sBufsM / 1024).append("kb d: ") + .append(dBufsM / 1024).append("kb)").append("\n"); } else { store.append("Total heap memory held: ").append(heapMem / 1024).append("kb\n"); - store.append("Only heap memory available, if you want to monitor direct memory use BufferUtils.setTrackDirectMemoryEnabled(true) during initialization.").append("\n"); + store.append( + "Only heap memory available, if you want to monitor direct memory use BufferUtils.setTrackDirectMemoryEnabled(true) during initialization.") + .append("\n"); } if (printStout) { System.out.println(store.toString()); } } - private static Method cleanerMethod = null; - private static Method cleanMethod = null; - private static Method viewedBufferMethod = null; - private static Method freeMethod = null; - - private static Method loadMethod(String className, String methodName) { - try { - Method method = Class.forName(className).getMethod(methodName); - method.setAccessible(true); - return method; - } catch (NoSuchMethodException ex) { - return null; // the method was not found - } catch (SecurityException ex) { - return null; // setAccessible not allowed by security policy - } catch (ClassNotFoundException ex) { - return null; // the direct buffer implementation was not found - } - } - - static { - // Oracle JRE / OpenJDK - cleanerMethod = loadMethod("sun.nio.ch.DirectBuffer", "cleaner"); - cleanMethod = loadMethod("sun.misc.Cleaner", "clean"); - viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "viewedBuffer"); - if (viewedBufferMethod == null) { - // They changed the name in Java 7 (???) - viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "attachment"); - } - - // Apache Harmony - ByteBuffer bb = BufferUtils.createByteBuffer(1); - Class clazz = bb.getClass(); - try { - freeMethod = clazz.getMethod("free"); - } catch (NoSuchMethodException ex) { - } catch (SecurityException ex) { - } - } /** * Direct buffers are garbage collected by using a phantom reference and a - * reference queue. Every once a while, the JVM checks the reference queue and - * cleans the direct buffers. However, as this doesn't happen - * immediately after discarding all references to a direct buffer, it's - * easy to OutOfMemoryError yourself using direct buffers. This function - * explicitly calls the Cleaner method of a direct buffer. - * - * @param toBeDestroyed - * The direct buffer that will be "cleaned". Utilizes reflection. - * - */ + * reference queue. Every once a while, the JVM checks the reference queue + * and cleans the direct buffers. However, as this doesn't happen + * immediately after discarding all references to a direct buffer, it's easy + * to OutOfMemoryError yourself using direct buffers. + **/ public static void destroyDirectBuffer(Buffer toBeDestroyed) { if (!isDirect(toBeDestroyed)) { return; } - - try { - if (freeMethod != null) { - freeMethod.invoke(toBeDestroyed); - } else { - Object cleaner = cleanerMethod.invoke(toBeDestroyed); - if (cleaner != null) { - cleanMethod.invoke(cleaner); - } else { - // Try the alternate approach of getting the viewed buffer first - Object viewedBuffer = viewedBufferMethod.invoke(toBeDestroyed); - if (viewedBuffer != null) { - destroyDirectBuffer((Buffer) viewedBuffer); - } else { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "Buffer cannot be destroyed: {0}", toBeDestroyed); - } - } - } - } catch (IllegalAccessException ex) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); - } catch (IllegalArgumentException ex) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); - } catch (InvocationTargetException ex) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); - } catch (SecurityException ex) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); - } + allocator.destroyDirectBuffer(toBeDestroyed); } - + /* - * FIXME when java 1.5 supprt is dropped - replace calls to this method with Buffer.isDirect + * FIXME when java 1.5 supprt is dropped - replace calls to this method with + * Buffer.isDirect * - * Buffer.isDirect() is only java 6. Java 5 only have this method on Buffer subclasses : - * FloatBuffer, IntBuffer, ShortBuffer, ByteBuffer,DoubleBuffer, LongBuffer. - * CharBuffer has been excluded as we don't use it. + * Buffer.isDirect() is only java 6. Java 5 only have this method on Buffer + * subclasses : FloatBuffer, IntBuffer, ShortBuffer, + * ByteBuffer,DoubleBuffer, LongBuffer. CharBuffer has been excluded as we + * don't use it. * */ private static boolean isDirect(Buffer buf) { diff --git a/jme3-core/src/main/java/com/jme3/util/PrimitiveAllocator.java b/jme3-core/src/main/java/com/jme3/util/PrimitiveAllocator.java new file mode 100644 index 000000000..d49c02105 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/PrimitiveAllocator.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.nio.Buffer; +import java.nio.ByteBuffer; + +/** + * This class contains a primitve allocator with no special logic, should work + * on any jvm + */ +public final class PrimitiveAllocator implements BufferAllocator { + + @Override + public void destroyDirectBuffer(Buffer toBeDestroyed) { + // no exception by intent, as this way naivly written java7/8 + // applications wont crash on 9 assuming they can dispose buffers + System.err.println("Warning destroyBuffer not supported"); + } + + @Override + public ByteBuffer allocate(int size) { + return ByteBuffer.allocateDirect(size); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java b/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java new file mode 100644 index 000000000..de7e75bcb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class contains the reflection based way to remove DirectByteBuffers in + * java < 9, allocation is done via ByteBuffer.allocateDirect + */ +public final class ReflectionAllocator implements BufferAllocator { + private static Method cleanerMethod = null; + private static Method cleanMethod = null; + private static Method viewedBufferMethod = null; + private static Method freeMethod = null; + + static { + // Oracle JRE / OpenJDK + cleanerMethod = loadMethod("sun.nio.ch.DirectBuffer", "cleaner"); + cleanMethod = loadMethod("sun.misc.Cleaner", "clean"); + viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "viewedBuffer"); + if (viewedBufferMethod == null) { + // They changed the name in Java 7 (???) + viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "attachment"); + } + + // Apache Harmony (allocated directly, to not trigger allocator used + // logic in BufferUtils) + ByteBuffer bb = ByteBuffer.allocateDirect(1); + Class clazz = bb.getClass(); + try { + freeMethod = clazz.getMethod("free"); + } catch (NoSuchMethodException ex) { + } catch (SecurityException ex) { + } + } + + private static Method loadMethod(String className, String methodName) { + try { + Method method = Class.forName(className).getMethod(methodName); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException ex) { + return null; // the method was not found + } catch (SecurityException ex) { + return null; // setAccessible not allowed by security policy + } catch (ClassNotFoundException ex) { + return null; // the direct buffer implementation was not found + } + } + + @Override + /** + * This function explicitly calls the Cleaner method of a direct buffer. + * + * @param toBeDestroyed + * The direct buffer that will be "cleaned". Utilizes reflection. + * + */ + public void destroyDirectBuffer(Buffer toBeDestroyed) { + try { + if (freeMethod != null) { + freeMethod.invoke(toBeDestroyed); + } else { + Object cleaner = cleanerMethod.invoke(toBeDestroyed); + if (cleaner != null) { + cleanMethod.invoke(cleaner); + } else { + // Try the alternate approach of getting the viewed buffer + // first + Object viewedBuffer = viewedBufferMethod.invoke(toBeDestroyed); + if (viewedBuffer != null) { + destroyDirectBuffer((Buffer) viewedBuffer); + } else { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, + "Buffer cannot be destroyed: {0}", toBeDestroyed); + } + } + } + } catch (IllegalAccessException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (IllegalArgumentException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (InvocationTargetException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } catch (SecurityException ex) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); + } + } + + @Override + public ByteBuffer allocate(int size) { + return ByteBuffer.allocateDirect(size); + } + +}