@ -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 ( ) ;
// 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 ;
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 ;
/ * *
* 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 ;
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 ;
/ * *
* 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 ) {
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 ) ;
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 ;
}
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 ) {
BufferInfo info = new BufferInfo ( DoubleBuffer . class , buffer . capacity ( ) * 8 , buffer , BufferUtils . removeCollected ) ;
BufferUtils . trackedBuffers . put ( info , info ) ;
}
} * /
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 ;
long heapMem = Runtime . getRuntime ( ) . totalMemory ( ) - Runtime . getRuntime ( ) . freeMemory ( ) ;
boolean printStout = store = = null ;
if ( store = = null ) {
store = new StringBuilder ( ) ;
}
if ( trackDirectMemory ) {
// 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 ( ) ;
for ( BufferInfo b : BufferUtils . trackedBuffers . values ( ) ) {
if ( b . type = = ByteBuffer . class ) {
totalHeld + = b . size ;
bBufsM + = b . size ;
bBufs + + ;
} else if ( b instanceof FloatBuffer ) {
totalHeld + = b . capacity ( ) * 4 ;
fBufsM + = b . capacity ( ) * 4 ;
} else if ( b . type = = FloatBuffer . class ) {
totalHeld + = b . size ;
fBufsM + = b . size ;
fBufs + + ;
} else if ( b instanceof IntBuffer ) {
totalHeld + = b . capacity ( ) * 4 ;
iBufsM + = b . capacity ( ) * 4 ;
} else if ( b . type = = IntBuffer . class ) {
totalHeld + = b . size ;
iBufsM + = b . size ;
iBufs + + ;
} else if ( b instanceof ShortBuffer ) {
totalHeld + = b . capacity ( ) * 2 ;
sBufsM + = b . capacity ( ) * 2 ;
} else if ( b . type = = ShortBuffer . class ) {
totalHeld + = b . size ;
sBufsM + = b . size ;
sBufs + + ;
} else if ( b instanceof DoubleBuffer ) {
totalHeld + = b . capacity ( ) * 8 ;
dBufsM + = b . capacity ( ) * 8 ;
} else if ( b . type = = DoubleBuffer . class ) {
totalHeld + = b . size ;
dBufsM + = b . size ;
dBufs + + ;
}
}
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" ) ;
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 ( ) ;
}
}
}
}