Skip to content

Commit

Permalink
Close #749, memory leak of custom objects. ObjectOwnershipHelp didn't…
Browse files Browse the repository at this point in the history
… remove the custom object from the map properly. We need to remove it when retain count is 2, not 1, as the last retain is due to us allocating the object. Also added logging of retain/release of custom objects, see ObjCObject#logRetainRelease
  • Loading branch information
badlogic committed Mar 4, 2015
1 parent b4ac2e6 commit 10b678b
Showing 1 changed file with 44 additions and 15 deletions.
59 changes: 44 additions & 15 deletions objc/src/main/java/org/robovm/objc/ObjCObject.java
Expand Up @@ -325,6 +325,7 @@ public ObjCObjectRef(ObjCObject referent) {
static class ObjectOwnershipHelper {
private static final LongMap<Object> CUSTOM_OBJECTS = new LongMap<>();

private static volatile boolean logRetainRelease = false;
private static final long retainCount = Selector.register("retainCount").getHandle();
private static final long retain = Selector.register("retain").getHandle();
private static final long originalRetain = Selector.register("original_retain").getHandle();
Expand All @@ -333,7 +334,7 @@ static class ObjectOwnershipHelper {

private static final Method retainMethod;
private static final Method releaseMethod;

private static final LongMap<Long> customClassToNativeSuper = new LongMap<>();

static {
Expand All @@ -358,38 +359,42 @@ private static void registerCallbackMethod(long cls, long selector, long newSele
throw new Error(
"Failed to register callback method on the ObjectOwnershipHelper: class_addMethod(...) failed");
}

// find the super class that is a native class and cache it
long superClass = ObjCRuntime.class_getSuperclass(cls);
long nativeSuper = 0;
while(superClass != 0) {
while (superClass != 0) {
ObjCClass objCClass = ObjCClass.toObjCClass(superClass);
if(!objCClass.isCustom()) {
if (!objCClass.isCustom()) {
nativeSuper = superClass;
break;
}
superClass = ObjCRuntime.class_getSuperclass(superClass);
}
if(nativeSuper == 0) {
throw new Error("Couldn't find native super class for " + VM.newStringUTF(ObjCRuntime.class_getName(cls)));
if (nativeSuper == 0) {
throw new Error("Couldn't find native super class for "
+ VM.newStringUTF(ObjCRuntime.class_getName(cls)));
}
synchronized(customClassToNativeSuper) {
synchronized (customClassToNativeSuper) {
customClassToNativeSuper.put(cls, nativeSuper);
}
}

@Callback
private static @Pointer long retain(@Pointer long self, @Pointer long sel) {
if (ObjCRuntime.int_objc_msgSend(self, retainCount) <= 1) {
int count = ObjCRuntime.int_objc_msgSend(self, retainCount);
if (count <= 1) {
synchronized (CUSTOM_OBJECTS) {
ObjCClass cls = ObjCClass.toObjCClass(ObjCRuntime.object_getClass(self));
ObjCObject obj = ObjCObject.toObjCObject(cls.getType(), self, 0);
CUSTOM_OBJECTS.put(self, obj);
}
}
}
long cls = ObjCRuntime.object_getClass(self);
if (logRetainRelease)
logRetainRelease(cls, self, count, true);
long nativeSuper = 0;
synchronized(customClassToNativeSuper) {
synchronized (customClassToNativeSuper) {
nativeSuper = customClassToNativeSuper.get(cls);
}
Super sup = new Super(self, nativeSuper);
Expand All @@ -398,30 +403,40 @@ private static void registerCallbackMethod(long cls, long selector, long newSele

@Callback
private static void release(@Pointer long self, @Pointer long sel) {
if (ObjCRuntime.int_objc_msgSend(self, retainCount) == 1) {
int count = ObjCRuntime.int_objc_msgSend(self, retainCount);
if (count == 2) {
synchronized (CUSTOM_OBJECTS) {
CUSTOM_OBJECTS.remove(self);
}
}
}
long cls = ObjCRuntime.object_getClass(self);
if (logRetainRelease)
logRetainRelease(cls, self, count, false);
long nativeSuper = 0;
synchronized(customClassToNativeSuper) {
synchronized (customClassToNativeSuper) {
nativeSuper = customClassToNativeSuper.get(cls);
}
Super sup = new Super(self, nativeSuper);
ObjCRuntime.void_objc_msgSendSuper(sup.getHandle(), sel);
}

public static boolean isObjectRetained(ObjCObject object) {
synchronized (CUSTOM_OBJECTS) {
return CUSTOM_OBJECTS.containsKey(object.getHandle());
}
}

private static void logRetainRelease(long cls, long self, int count, boolean isRetain) {
String className = ObjCClass.getFromObject(cls).getType().getName();
System.err.println(String.format("[Debug] %s %s@0x%s, retain count: %d",
isRetain ? "Retained" : "Released", className, Long.toHexString(self), isRetain ? count + 1
: count - 1));
}
}

static class AssociatedObjectHelper {
private static final String STRONG_REFS_KEY = AssociatedObjectHelper.class.getName() + ".StrongRefs";

private static final int OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1;
private static final long RELEASE_LISTENER_CLASS;
private static final String OWNER_IVAR_NAME = "value";
Expand Down Expand Up @@ -583,4 +598,18 @@ public Super(long receiver, long objcClass) {
@StructMember(1)
public native Super objCClass(@Pointer long objCClass);
}

/**
* Sets whether retain/release of custom {@link ObjCObject} should be logged
* to the console to identify retain/release leaks. Note that the GC has to
* be able to collect the custom object for the final release to be
* triggered.</p>
*
* The output logs the class, memory address and retain count after the
* release/retain invocation. You can use the memory address to inspect
* custom objects in Instruments.
*/
public static void logRetainRelease(boolean enabled) {
ObjectOwnershipHelper.logRetainRelease = enabled;
}
}

0 comments on commit 10b678b

Please sign in to comment.