BufferUtils : Apply changes made by Empire Phenix to properly track direct memory. This feature is defaulted to off.
git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9669 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
parent
fc3a4a4471
commit
a04d5dde01
@ -31,18 +31,29 @@
|
||||
*/
|
||||
package com.jme3.util;
|
||||
|
||||
import com.jme3.math.*;
|
||||
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.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
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;
|
||||
|
||||
/**
|
||||
* <code>BufferUtils</code> is a helper class for generating nio buffers from
|
||||
* jME data classes such as Vectors and ColorRGBA.
|
||||
@ -52,28 +63,20 @@ import java.util.logging.Logger;
|
||||
*/
|
||||
public final class BufferUtils {
|
||||
|
||||
private static final Map<Buffer, Object> trackingHash = Collections.synchronizedMap(new WeakHashMap<Buffer, Object>());
|
||||
private static final Object ref = new Object();
|
||||
private static boolean trackDirectMemory = false;
|
||||
private static ReferenceQueue<Buffer> removeCollected = new ReferenceQueue<Buffer>();
|
||||
private static ConcurrentHashMap<BufferInfo, BufferInfo> trackedBuffers = new ConcurrentHashMap<BufferInfo, BufferInfo>();
|
||||
static ClearReferences cleanupthread;
|
||||
|
||||
// Note: a WeakHashMap is really bad here since the hashCode() and
|
||||
// equals() behavior of buffers will vary based on their contents.
|
||||
// As it stands, put()'ing an empty buffer will wipe out the last
|
||||
// empty buffer with the same size. So any tracked memory calculations
|
||||
// could be lying.
|
||||
// Besides, the hashmap behavior isn't even being used here and
|
||||
// yet the expense is still incurred. For example, a newly allocated
|
||||
// 10,000 byte buffer will iterate through the whole buffer of 0's
|
||||
// to calculate the hashCode and then potentially do it again to
|
||||
// calculate the equals()... which by the way is guaranteed for
|
||||
// every empty buffer of an existing size since they will always produce
|
||||
// the same hashCode().
|
||||
// It would be better to just keep a straight list of weak references
|
||||
// and clean out the dead every time a new buffer is allocated.
|
||||
// WeakHashMap is doing that anyway... so there is no extra expense
|
||||
// incurred.
|
||||
// Recommend a ConcurrentLinkedQueue of WeakReferences since it
|
||||
// supports the threading semantics required with little extra overhead.
|
||||
private static final boolean trackDirectMemory = false;
|
||||
/**
|
||||
* 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
|
||||
@ -99,53 +102,58 @@ public final class BufferUtils {
|
||||
}
|
||||
|
||||
private static void onBufferAllocated(Buffer buffer) {
|
||||
/*
|
||||
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
|
||||
int initialIndex = 0;
|
||||
/**
|
||||
* 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) { } }
|
||||
*/
|
||||
if (BufferUtils.trackDirectMemory) {
|
||||
|
||||
for (int i = 0; i < stackTrace.length; i++){
|
||||
if (!stackTrace[i].getClassName().equals(BufferUtils.class.getName())){
|
||||
initialIndex = i;
|
||||
break;
|
||||
if (BufferUtils.cleanupthread == null) {
|
||||
BufferUtils.cleanupthread = new ClearReferences();
|
||||
BufferUtils.cleanupthread.start();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if (buffer instanceof ByteBuffer) {
|
||||
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);
|
||||
BufferUtils.trackedBuffers.put(info, info);
|
||||
} else if (buffer instanceof IntBuffer) {
|
||||
size = 4;
|
||||
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);
|
||||
BufferUtils.trackedBuffers.put(info, info);
|
||||
} else if (buffer instanceof DoubleBuffer) {
|
||||
size = 8;
|
||||
BufferInfo info = new BufferInfo(DoubleBuffer.class, buffer.capacity() * 8, buffer, BufferUtils.removeCollected);
|
||||
BufferUtils.trackedBuffers.put(info, info);
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
}*/
|
||||
|
||||
if (trackDirectMemory){
|
||||
trackingHash.put(buffer, ref);
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,9 +169,9 @@ public final class BufferUtils {
|
||||
return null;
|
||||
}
|
||||
FloatBuffer buff = createFloatBuffer(3 * data.length);
|
||||
for (int x = 0; x < data.length; x++) {
|
||||
if (data[x] != null) {
|
||||
buff.put(data[x].x).put(data[x].y).put(data[x].z);
|
||||
for (Vector3f element : data) {
|
||||
if (element != null) {
|
||||
buff.put(element.x).put(element.y).put(element.z);
|
||||
} else {
|
||||
buff.put(0).put(0).put(0);
|
||||
}
|
||||
@ -183,9 +191,9 @@ public final class BufferUtils {
|
||||
return null;
|
||||
}
|
||||
FloatBuffer buff = createFloatBuffer(4 * data.length);
|
||||
for (int x = 0; x < data.length; x++) {
|
||||
if (data[x] != null) {
|
||||
buff.put(data[x].getX()).put(data[x].getY()).put(data[x].getZ()).put(data[x].getW());
|
||||
for (Quaternion element : data) {
|
||||
if (element != null) {
|
||||
buff.put(element.getX()).put(element.getY()).put(element.getZ()).put(element.getW());
|
||||
} else {
|
||||
buff.put(0).put(0).put(0);
|
||||
}
|
||||
@ -496,9 +504,9 @@ public final class BufferUtils {
|
||||
return null;
|
||||
}
|
||||
FloatBuffer buff = createFloatBuffer(2 * data.length);
|
||||
for (int x = 0; x < data.length; x++) {
|
||||
if (data[x] != null) {
|
||||
buff.put(data[x].x).put(data[x].y);
|
||||
for (Vector2f element : data) {
|
||||
if (element != null) {
|
||||
buff.put(element.x).put(element.y);
|
||||
} else {
|
||||
buff.put(0).put(0);
|
||||
}
|
||||
@ -1140,50 +1148,54 @@ public final class BufferUtils {
|
||||
|
||||
public static void printCurrentDirectMemory(StringBuilder store) {
|
||||
long totalHeld = 0;
|
||||
// make a new set to hold the keys to prevent concurrency issues.
|
||||
ArrayList<Buffer> bufs = new ArrayList<Buffer>(trackingHash.keySet());
|
||||
int fBufs = 0, bBufs = 0, iBufs = 0, sBufs = 0, dBufs = 0;
|
||||
int fBufsM = 0, bBufsM = 0, iBufsM = 0, sBufsM = 0, dBufsM = 0;
|
||||
for (Buffer b : bufs) {
|
||||
if (b instanceof ByteBuffer) {
|
||||
totalHeld += b.capacity();
|
||||
bBufsM += b.capacity();
|
||||
bBufs++;
|
||||
} else if (b instanceof FloatBuffer) {
|
||||
totalHeld += b.capacity() * 4;
|
||||
fBufsM += b.capacity() * 4;
|
||||
fBufs++;
|
||||
} else if (b instanceof IntBuffer) {
|
||||
totalHeld += b.capacity() * 4;
|
||||
iBufsM += b.capacity() * 4;
|
||||
iBufs++;
|
||||
} else if (b instanceof ShortBuffer) {
|
||||
totalHeld += b.capacity() * 2;
|
||||
sBufsM += b.capacity() * 2;
|
||||
sBufs++;
|
||||
} else if (b instanceof DoubleBuffer) {
|
||||
totalHeld += b.capacity() * 8;
|
||||
dBufsM += b.capacity() * 8;
|
||||
dBufs++;
|
||||
}
|
||||
}
|
||||
long heapMem = Runtime.getRuntime().totalMemory()
|
||||
- Runtime.getRuntime().freeMemory();
|
||||
long heapMem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
|
||||
boolean printStout = store == null;
|
||||
if (store == null) {
|
||||
store = new StringBuilder();
|
||||
}
|
||||
store.append("Existing buffers: ").append(bufs.size()).append("\n");
|
||||
if (trackDirectMemory) {
|
||||
// make a new set to hold the keys to prevent concurrency issues.
|
||||
int fBufs = 0, bBufs = 0, iBufs = 0, sBufs = 0, dBufs = 0;
|
||||
int fBufsM = 0, bBufsM = 0, iBufsM = 0, sBufsM = 0, dBufsM = 0;
|
||||
for (BufferInfo b : BufferUtils.trackedBuffers.values()) {
|
||||
if (b.type == ByteBuffer.class) {
|
||||
totalHeld += b.size;
|
||||
bBufsM += b.size;
|
||||
bBufs++;
|
||||
} else if (b.type == FloatBuffer.class) {
|
||||
totalHeld += b.size;
|
||||
fBufsM += b.size;
|
||||
fBufs++;
|
||||
} else if (b.type == IntBuffer.class) {
|
||||
totalHeld += b.size;
|
||||
iBufsM += b.size;
|
||||
iBufs++;
|
||||
} else if (b.type == ShortBuffer.class) {
|
||||
totalHeld += b.size;
|
||||
sBufsM += b.size;
|
||||
sBufs++;
|
||||
} else if (b.type == DoubleBuffer.class) {
|
||||
totalHeld += b.size;
|
||||
dBufsM += b.size;
|
||||
dBufs++;
|
||||
}
|
||||
}
|
||||
|
||||
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("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");
|
||||
} 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");
|
||||
}
|
||||
if (printStout) {
|
||||
System.out.println(store.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static final AtomicBoolean loadedMethods = new AtomicBoolean(false);
|
||||
private static Method cleanerMethod = null;
|
||||
private static Method cleanMethod = null;
|
||||
private static Method viewedBufferMethod = null;
|
||||
@ -1203,7 +1215,14 @@ public final class BufferUtils {
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
private static void loadCleanerMethods() {
|
||||
// If its already true, exit, if not, set it to true.
|
||||
if (BufferUtils.loadedMethods.getAndSet(true)) {
|
||||
return;
|
||||
}
|
||||
// This could potentially be called many times if used from multiple
|
||||
// threads
|
||||
synchronized (BufferUtils.loadedMethods) {
|
||||
// Oracle JRE / OpenJDK
|
||||
cleanerMethod = loadMethod("sun.nio.ch.DirectBuffer", "cleaner");
|
||||
cleanMethod = loadMethod("sun.misc.Cleaner", "clean");
|
||||
@ -1222,6 +1241,7 @@ public final class BufferUtils {
|
||||
} catch (SecurityException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct buffers are garbage collected by using a phantom reference and a
|
||||
@ -1240,6 +1260,8 @@ public final class BufferUtils {
|
||||
return;
|
||||
}
|
||||
|
||||
BufferUtils.loadCleanerMethods();
|
||||
|
||||
try {
|
||||
if (freeMethod != null) {
|
||||
freeMethod.invoke(toBeDestroyed);
|
||||
@ -1267,4 +1289,36 @@ public final class BufferUtils {
|
||||
Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BufferInfo extends PhantomReference<Buffer> {
|
||||
|
||||
private Class type;
|
||||
private int size;
|
||||
|
||||
public BufferInfo(Class type, int size, Buffer referent, ReferenceQueue<? super Buffer> q) {
|
||||
super(referent, q);
|
||||
this.type = type;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClearReferences extends Thread {
|
||||
|
||||
ClearReferences() {
|
||||
this.setDaemon(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
Reference<? extends Buffer> toclean = BufferUtils.removeCollected.remove();
|
||||
BufferUtils.trackedBuffers.remove(toclean);
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user