Bug 200399 - after returning advice = memory leak
Summary: after returning advice = memory leak
Status: NEW
Alias: None
Product: AspectJ
Classification: Tools
Component: Runtime (show other bugs)
Version: DEVELOPMENT   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: aspectj inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-08-17 12:37 EDT by Joshua Caplan CLA
Modified: 2013-06-24 11:06 EDT (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Joshua Caplan CLA 2007-08-17 12:37:57 EDT
Build ID: M20060921-0945

Steps To Reproduce:
Run the following sample program:

public aspect StrongRef {
	// this advice leaks o!
	after() returning ( Object o ) : call( Object.new( .. ) ) {}
	
	public static void main( String[] args ) {
		Map< Object, Object > mWeak = new WeakHashMap< Object, Object >();
		mWeak.put( new Object(), new Object() );
		flushMemory();
		System.out.println( "object was " + ( 0 != mWeak.size() ? "not " : "" ) + "reclaimed" );
	}
	public static void flushMemory()	{
		Vector< byte[] > v = new Vector< byte[] >();
		int count = 0;
		int size = 1 << 20;
		while ( size > 1 ) try {
			for (; true ; count++)
				v.addElement( new byte[ size ] );
		} catch( OutOfMemoryError bounded ){
			size >>= 1;
		}
		v = null; //release for GC
		v = new Vector< byte[] >();
	}
}


More information:
run as is, will output "object was not reclaimed".
comment out the "returning ( Object o )" in the after advice, and the output is "object was reclaimed".
Comment 1 Joshua Caplan CLA 2007-08-17 14:28:34 EDT
the following workaround is only acceptable if you wish to advise the constructor calls of a class that is part of the same project:

        // this advice doesn't leak mo
        after( MyObject mo ) : execution( MyObject.new( .. ) ) && this ( mo ) {}
Comment 2 Andrew Clement CLA 2008-01-22 16:35:00 EST
I always find testing for memory leaks is hard - especially when references are involved since they never seem to behave as described...  Here is a variation of your program which should have the same problem:

public aspect StrongRef {
        // this advice leaks o!
        after() returning (Object o) : call( Object.new( .. ) ) {}

        public static void main( String[] args ) {
                Map< Object, Object > mWeak = new WeakHashMap< Object, Object >();
                for (int i=0;i<5;i++) {
                  mWeak.put( new Object(), new Object() );
                }
                flushMemory();
                System.out.println("mWeak size is "+mWeak.size());
                System.out.println( "object was " + ( 0 != mWeak.size() ? "not " : "" ) + "reclaimed" );
        }

        public static void flushMemory()        {
                Vector< byte[] > v = new Vector< byte[] >();
                int count = 0; 
                int size = 1 << 20;
                while ( size > 1 ) try {
                        for (; true ; count++)
                                v.addElement( new byte[ size ] );
                } catch( OutOfMemoryError bounded ){ 
                        size >>= 1;
                }
                v = null; //release for GC
                v = new Vector< byte[] >();
        }
}   

but it doesn't it runs fine:

pr200399\>ajc -1.5 -showWeaveInfo StrongRef.java
Join point 'constructor-call(void java.lang.Object.<init>())' in Type 'StrongRef' (StrongRef.java:10) advised by afterReturning advice from 'StrongRef' (StrongRef.java:5)

Join point 'constructor-call(void java.lang.Object.<init>())' in Type 'StrongRef' (StrongRef.java:10) advised by afterReturning advice from 'StrongRef' (StrongRef.java:5)

pr200399\>java StrongRef
mWeak size is 0
object was reclaimed

Looking at the generated code, I can't see where the leak would be anyway.  I might believe there was a leak if you were using a different instantiation model than singleton (perthis or something) - but from the program I just ran and the generated code, I don't think there is anything to fix and the collection of references is up to the VM. (i am on a 1.5.0 on the Mac)

I was then trying to take a heap dump and use jhat to investigate where the references were, to ensure there were no strong ones - but jhat on the mac is crashing with NPEs, stupid thing.

Comment 3 Andrew Clement CLA 2008-06-11 19:31:20 EDT
I can see the difference now between the use of after() and after() returning.

When using after() returning, the result of the ctor call to Object.new() is stored in a local variable in the method so that it can be passed to the advice.  The act of anchoring it in the local variable means it is not available for garbage collection.  Your program does not let the new Object instance go out of scope before attempting to fill up memory, and so they don't disappear.  If I change your method:

public static void main( String[] args ) {
                Map< Object, Object > mWeak = new WeakHashMap< Object, Object>();
                mWeak.put( new Object(), new Object() );
                flushMemory();
                System.out.println( "object was " + ( 0 != mWeak.size() ? "not" : "" ) + "reclaimed" );
}

to

public static void main( String[] args ) {
                Map< Object, Object > mWeak = new WeakHashMap< Object, Object>();
try {
                mWeak.put( new Object(), new Object() );
} catch (Throwable t) {}
                flushMemory();
                System.out.println( "object was " + ( 0 != mWeak.size() ? "not" : "" ) + "reclaimed" );
}

then it runs fine, because the local variables used to hold the new Objects() are out of scope by the time the flushMemory is invoked.

Similarly, my test program worked because the scope of the variables holding onto the objects was only within the loop.

Do you want me to null out the local variables after an advice call on the off chance they are weak referenceable?  I'd rather not, but if you can show me a real scenario where this is an issue, we can look to do that.

Comment 4 Andrew Clement CLA 2013-06-24 11:06:33 EDT
unsetting the target field which is currently set for something already released