From: Brian C. <co...@ey...> - 2009-01-21 22:10:20
|
Grr, entourage didn’t like me copy and pasting code from Emacs, deleted all my newlines. Here’s the block of code I meant to send: final class NativeImage3 extends WeakReference<Image3> { static private ConcurrentMap<NativeImage3,NativeImage3> refMap; static private AtomicInteger numEnqueued; void dispose() { refMap.remove(this); disposeNative(); } public boolean enqueue() { super.enqueue(); numEnqueued.incrementAndGet(); } NativeImage3(Image3 img) { super(img, refQueue); refMap.put(this,this); if (refMap.size()/2 < numEnqueued.get()) {// cleanup thread getting hammered, help it out NativeImage3 nativeImg = (NativeImage3) refQueue.poll(); if (nativeImg != null) { nativeImg.dispose(); numEnqueued.decrementAndGet(); } } } } static void drainRefQueueLoop() { ReferenceQueue<Image3> refQueue = NativeImage3.referenceQueue(); while (true) { NativeImage3 nativeImg = (NativeImage3) refQueue.remove(); nativeImg.dispose(); } } On 1/21/09 2:15 PM, "Brian Cole" <co...@ey...> wrote: Accidentally only sent my reply back to William. Attached it at the end of this update for general knowledge. Our current status is we noticed a race condition in the example posted at http://java.sun.com/developer/technicalArticles/javase/finalization/ final class NativeImage3 extends WeakReference<Image3> { static private List<NativeImage3> refList; void dispose() { refList.remove(this); disposeNative(); } NativeImage3(Image3 img) { super(img, refQueue); refList.add(this); } } Since the dispose method is called by a different “cleanup” thread there is a race on the refList between it and the main thread calling the constructor. RefList.remove is too slow anyway, and this allows objects to again be created faster than they can be cleaned up. Adding cleanup to the constructor is way too slow. So we’re going to try a two pronged approach, cleanup thread and constructor cleanup only when it sees the cleanup thread getting hammered. This will require overriding the WeakReference.enqueue method to keep track of the number of objects waiting to be cleaned up by the cleanup thread. When the number of objects waiting to be disposed goes above a certain limit the constructor will kick in and start cleaning up objects as well. The limit is arbitrary, we were thinking something like the following: final class NativeImage3 extends WeakReference<Image3> { static private ConcurrentMap<NativeImage3,NativeImage3> refMap; static private AtomicInteger numEnqueued; void dispose() { refMap.remove(this); disposeNative(); } public boolean enqueue() { super.enqueue(); numEnqueued.incrementAndGet(); } NativeImage3(Image3 img) { super(img, refQueue); refMap.put(this,this); if (refMap.size()/2 < numEnqueued.get()) {// cleanup thread getting hammered, help it out NativeImage3 nativeImg = (NativeImage3) refQueue.poll(); if (nativeImg != null) { nativeImg.dispose(); numEnqueued.decrementAndGet(); } } } } Combined with the regular cleanup thread. static void drainRefQueueLoop() { ReferenceQueue<Image3> refQueue = NativeImage3.referenceQueue(); while (true) { NativeImage3 nativeImg = (NativeImage3) refQueue.remove(); nativeImg.dispose(); } } Question about shutdown though. With finalizers there is a way to guarantee that all finalizers are called before program exit. Is there a way to code up the cleanup thread so that it will force all native objects in the ReferenceQueue to be cleaned up before allowing the thread to exit and hence the program? Otherwise I will have to be selective about which objects use finalizers and which use this non-finalizer automatic cleanup. -Brian On 1/20/09 6:45 PM, "Brian Cole" <co...@ey...> wrote: Brian Cole wrote: > Hi All, > > We’ve been wrestling with the Java memory problem for awhile like other > people: > http://sourceforge.net/mailarchive/message.php?msg_name=5656c890806091140q126737awe009110e00aa4c3%40mail.gmail.com > > We’ve implemented the java heap allocation as described here: > http://www.swig.org/Doc1.3/Java.html#java_heap_allocations. Note, there > are some bugs with that implementation and possible pitfalls with > overloading operator new that have to be dealt with to do it properly: > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1976.html#problem_01 > Please submit a patch so that the docs can be fixed. We’ll submit a patch when we’re done fixing the problem as a whole as we may have to alter it some more. The main bug we found is that JNU_GetEnv() could fail if the JVM environment has already finished shutting down, but C++ destructors are still being called which need to free memory. In that case the memory has to be left to leak under the assumption that since the JVM is shutting down the operating system will reclaim the memory anyway. The operator new/delete linking problems have to be dealt with on a per-platform basis since you have to force the linker to make operator new/delete local to the shared library. Otherwise you can have mismatches with other shared libraries that use C++ and are loaded before your library. This problem rears it’s ugly head frequently on Mac OS X where there are some built in Java libraries that use C++, though it should be possible whenever mixing 3rd party libraries that use C++. > This helps the issue greatly, making sure all allocations are tracked by > the JVM, giving us more deterministic memory performance. However, we > still have applications that can force the JVM into a very unhappy > state. The root of the issue is SWIG’s use of finalizers to make sure > objects are properly deleted as pointed out in the JNI programming manual. > Didn't you find performance rather poor? Yes, but we do some internal memory pooling which alleviates most of that problem. Using VisualVM we can watch the heap getting full of Finalizer objects, and sometimes, not always, we get OutOfMemory errors. We can also view the Finalizer thread churning at full speed, it just can’t keep up sometimes. > <snippet page 126 in java.sun.com/docs/books/jni/download/jni.pdf> > Defining a finalize method is a proper safeguard, but /you should never > rely/ > /on finalizers as the sole means of freeing native data structures/.The > reason is that > the native data structures may consume much more resources than their peer > instances. The Java virtual machine may not garbage collect and finalize > instances > of peer classes fast enough to free up their native counterparts. > Defining a finalizer has performance consequences as well. It is typically > slower to create and reclaim instances of classes with finalizers than to > create and > reclaim those without finalizers. > </snippet> > > Has anyone tried to use any methods to avoid finalizers entirely, and > still not burden users with explicit cleanup. I just stumbled into this > today: > http://java.sun.com/developer/technicalArticles/javase/finalization/ It > uses a another proxy class layer and an explicit cleanup thread. > Yes, that is a great paper. I met Tony when he presented this work to us a year or so back but have never had time to implement it. I'd be very interested to see how you get on. It is probably possible to implement this using the current SWIG features/typemaps/pragmas etc. > Going to try and hijack this into our SWIG setup, but I wanted to throw > the idea out there for comment. Maybe worth incorporating in SWIG as an > alternative to finalizers? > If it works out well for you, we can incorporate this as alternative set of typemaps and ship it with SWIG. William Good to know we’re not on a wild goose chase. Did he design it with SWIG in mind? We were thinking it would be doable with the current SWIG features as well. For a proof of concept we’re going to hardcode the functionality into an object we know is causing us problems. Then move forward with a more general approach. The conceptual issue we’re struggling with now is how to force SWIG to create two proxy classes. One needs to hold the long swigCPtr which inherits from WeakReference. The other which simply contains a reference to the former class. I see how to make SWIG inherit from a universal base class with the following: http://www.swig.org/Doc1.3/Java.html#void_pointers The first proxy class would be created with a swig interface like the following: %typemap(javabase) SWIGTYPE, SWIGTYPE *, SWIGTYPE &, SWIGTYPE [], SWIGTYPE (CLASS::*) "SWIGJavaBase" %typemap(javacode) SWIGTYPE, SWIGTYPE *, SWIGTYPE &, SWIGTYPE [], SWIGTYPE (CLASS::*) %{ protected long getPointer() { return swigCPtr; } %} final class SWIGJavaBase extends WeakReference<JavaBase> { void dispose() { refList.remove(this); delete(); // calls the SWIG generated delete which will call the actual C++ dtor } static private ReferenceQueue<JavaBase> refQueue; static private List<SWIGJavaBase> refList; static ReferenceQueue<JavaBase> referenceQueue() { return refQueue; } SWIGJavaBase(JavaBase obj) { super(obj, refQueue); refList.add(this); } } The second proxy class would have the following base: public class JavaBase { private SWIGJavaBase obj; public void dispose() { obj.dispose(); } } So if a C++ class is defined as follows: class Foo { public: int spam(int num, Foo* foo); }; I need SWIG to generate both of the following proxy classes: public class FooNative extends SWIGJavaBase { private long swigCPtr; protected boolean swigCMemOwn; protected FooNative(long cPtr, boolean cMemoryOwn) { swigCMemOwn = cMemoryOwn; swigCPtr = cPtr; } protected static long getCPtr(FooNative obj) { return (obj == null) ? 0 : obj.swigCPtr; } ... // no finalize method public synchronized void delete() { if(swigCPtr != 0 && swigCMemOwn) { swigCMemOwn = false; exampleJNI.delete_Foo(swigCPtr); } swigCPtr = 0; } public int spam(int num, Foo foo) { return exampleJNI.Foo_spam(swigCPtr, this, num, Foo.getCPtr(foo), foo); } public FooNative(Foo obj) { super(obj); this(exampleJNI.new_Foo(), true); } } public class Foo extends JavaBase { private FooNative obj; Foo() { obj = new FooNative(this); } } Any ideas on forcing SWIG to give me two proxy classes (almost like Python SWIG used to do)? Also, they both have to derive from my specially defined pure Java base classes. Then there’s the separate decision of whether cleanup should be handled by a separate thread or the object constructor. Thoughts? We’ll keep pushing forward. Our hope is to provide seamless Java support without requiring our users to be experts in Java garbage collection. Perhaps too tall an order. Thanks -Brian |