Skip to content

Commit 393079a

Browse files
committed
Merge pull request realm#1292 from realm/mc-linkview-leak
Release LinkView native pointers
2 parents e490afe + 13d865f commit 393079a

7 files changed

Lines changed: 88 additions & 35 deletions

File tree

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Fixed a bug making it impossible to convert a field to become required during a migration (#1695).
44
* Fixed a bug making it impossible to read Realms created using primary keys and created by iOS (#1703).
55
* Fixed some memory leaks when an Exception is thrown (#1730).
6+
* Fixed a memory leak when using relationships. (#1285)
67
* RealmQuery.isNull() and RealmQuery.isNotNull() now throw IllegalArgumentException instead of RealmError if the fieldname is a linked field and the last element is a link (#1693).
78

89
0.84.1

realm/realm-library/src/androidTest/java/io/realm/RealmTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,13 +1802,14 @@ public void testMutableMethodsOutsideTransactions() throws JSONException, IOExce
18021802
// TODO: re-introduce this test mocking the ReferenceQueue instead of relying on the GC
18031803
/* // Check that FinalizerRunnable can free native resources (phantom refs)
18041804
public void testReferenceCleaning() throws NoSuchFieldException, IllegalAccessException {
1805+
testRealm.close();
18051806
18061807
RealmConfiguration config = new RealmConfiguration.Builder(getContext()).name("myown").build();
18071808
Realm.deleteRealm(config);
18081809
testRealm = Realm.getInstance(config);
18091810
18101811
// Manipulate field accessibility to facilitate testing
1811-
Field realmFileReference = RealmBase.class.getDeclaredField("sharedGroupManager");
1812+
Field realmFileReference = BaseRealm.class.getDeclaredField("sharedGroupManager");
18121813
realmFileReference.setAccessible(true);
18131814
Field contextField = SharedGroup.class.getDeclaredField("context");
18141815
contextField.setAccessible(true);
@@ -1821,7 +1822,7 @@ public void testReferenceCleaning() throws NoSuchFieldException, IllegalAccessEx
18211822
io.realm.internal.Context context = (io.realm.internal.Context) contextField.get(realmFile.getSharedGroup());
18221823
assertNotNull(context);
18231824
1824-
List<Reference<?>> rowReferences = (List<Reference<?>>) rowReferencesField.get(context);
1825+
Map<Reference<?>, Integer> rowReferences = (Map<Reference<?>, Integer>) rowReferencesField.get(context);
18251826
assertNotNull(rowReferences);
18261827
18271828
// insert some rows, then give the thread some time to cleanup
@@ -1846,6 +1847,7 @@ public void testReferenceCleaning() throws NoSuchFieldException, IllegalAccessEx
18461847
numberOfRetries++;
18471848
System.gc();
18481849
}
1850+
context.cleanNativeReferences();
18491851
18501852
// we can't guarantee that all references have been GC'ed but we should detect a decrease
18511853
boolean isDecreasing = rowReferences.size() < totalNumberOfReferences;

realm/realm-library/src/main/java/io/realm/internal/CheckedRow.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ private CheckedRow(UncheckedRow row) {
5151
public static CheckedRow get(Context context, Table table, long index) {
5252
long nativeRowPointer = table.nativeGetRowPtr(table.nativePtr, index);
5353
CheckedRow row = new CheckedRow(context, table, nativeRowPointer);
54-
context.rowReferences.put(new NativeObjectReference(row, context.referenceQueue), context.ROW_REFERENCES_VALUE);
54+
context.rowReferences.put(new UncheckedRowNativeObjectReference(row, context.referenceQueue),
55+
Context.NATIVE_REFERENCES_VALUE);
5556
return row;
5657
}
5758

@@ -64,9 +65,11 @@ public static CheckedRow get(Context context, Table table, long index) {
6465
* @return a checked instance of {@link Row} for the {@link LinkView} and index specified.
6566
*/
6667
public static CheckedRow get(Context context, LinkView linkView, long index) {
67-
long nativeRowPointer = linkView.nativeGetRow(linkView.nativeLinkViewPtr, index);
68-
CheckedRow row = new CheckedRow(context, linkView.parent.getLinkTarget(linkView.columnIndexInParent), nativeRowPointer);
69-
context.rowReferences.put(new NativeObjectReference(row, context.referenceQueue), context.ROW_REFERENCES_VALUE);
68+
long nativeRowPointer = linkView.nativeGetRow(linkView.nativePointer, index);
69+
CheckedRow row = new CheckedRow(context, linkView.parent.getLinkTarget(linkView.columnIndexInParent),
70+
nativeRowPointer);
71+
context.rowReferences.put(new UncheckedRowNativeObjectReference(row, context.referenceQueue),
72+
Context.NATIVE_REFERENCES_VALUE);
7073
return row;
7174
}
7275

realm/realm-library/src/main/java/io/realm/internal/Context.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public class Context {
3030
// whose disposal need to be handed over from the garbage
3131
// collection thread to the users thread.
3232

33-
// Reserved to be used only as a placeholder by rowReferences Map to avoid autoboxing allocations
34-
static final Integer ROW_REFERENCES_VALUE = 0;
33+
// Reserved to be used only as a placeholder by native references Map to avoid autoboxing allocations
34+
static final Integer NATIVE_REFERENCES_VALUE = 0;
3535

3636
private List<Long> abandonedTables = new ArrayList<Long>();
3737
private List<Long> abandonedTableViews = new ArrayList<Long>();
@@ -62,14 +62,14 @@ public void executeDelayedDisposal() {
6262
}
6363
abandonedQueries.clear();
6464

65-
cleanRows();
65+
cleanNativeReferences();
6666
}
6767
}
6868

69-
public void cleanRows() {
69+
public void cleanNativeReferences() {
7070
NativeObjectReference reference = (NativeObjectReference) referenceQueue.poll();
7171
while (reference != null) {
72-
UncheckedRow.nativeClose(reference.nativePointer);
72+
reference.clear();
7373
rowReferences.remove(reference);
7474
reference = (NativeObjectReference) referenceQueue.poll();
7575
}

realm/realm-library/src/main/java/io/realm/internal/LinkView.java

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,39 @@
1616

1717
package io.realm.internal;
1818

19+
import java.lang.ref.ReferenceQueue;
20+
1921
/**
2022
* The LinkView class represents a core {@link ColumnType#LINK_LIST}.
2123
*/
22-
public class LinkView {
24+
public class LinkView extends NativeObject {
25+
26+
private static class LinkViewReference extends NativeObjectReference {
27+
28+
public LinkViewReference(NativeObject referent,
29+
ReferenceQueue<? super NativeObject> referenceQueue) {
30+
super(referent, referenceQueue);
31+
}
32+
33+
@Override
34+
protected void cleanup() {
35+
nativeClose(nativePointer);
36+
}
37+
}
2338

2439
private final Context context;
25-
final long nativeLinkViewPtr;
2640
final Table parent;
2741
final long columnIndexInParent;
2842

2943
public LinkView(Context context, Table parent, long columnIndexInParent, long nativeLinkViewPtr) {
3044
this.context = context;
3145
this.parent = parent;
3246
this.columnIndexInParent = columnIndexInParent;
33-
this.nativeLinkViewPtr = nativeLinkViewPtr;
47+
this.nativePointer = nativeLinkViewPtr;
48+
49+
context.cleanNativeReferences();
50+
context.rowReferences.put(new LinkViewReference(this, context.referenceQueue),
51+
Context.NATIVE_REFERENCES_VALUE);
3452
}
3553

3654
/**
@@ -59,51 +77,51 @@ public CheckedRow getCheckedRow(long index) {
5977
}
6078

6179
public long getTargetRowIndex(long pos) {
62-
return nativeGetTargetRowIndex(nativeLinkViewPtr, pos);
80+
return nativeGetTargetRowIndex(nativePointer, pos);
6381
}
6482

6583
public void add(long rowIndex) {
6684
checkImmutable();
67-
nativeAdd(nativeLinkViewPtr, rowIndex);
85+
nativeAdd(nativePointer, rowIndex);
6886
}
6987

7088
public void insert(long pos, long rowIndex) {
7189
checkImmutable();
72-
nativeInsert(nativeLinkViewPtr, pos, rowIndex);
90+
nativeInsert(nativePointer, pos, rowIndex);
7391
}
7492

7593
public void set(long pos, long rowIndex) {
7694
checkImmutable();
77-
nativeSet(nativeLinkViewPtr, pos, rowIndex);
95+
nativeSet(nativePointer, pos, rowIndex);
7896
}
7997

8098
public void move(long oldPos, long newPos) {
8199
checkImmutable();
82-
nativeMove(nativeLinkViewPtr, oldPos, newPos);
100+
nativeMove(nativePointer, oldPos, newPos);
83101
}
84102

85103
public void remove(long pos) {
86104
checkImmutable();
87-
nativeRemove(nativeLinkViewPtr, pos);
105+
nativeRemove(nativePointer, pos);
88106
}
89107

90108
public void clear() {
91109
checkImmutable();
92-
nativeClear(nativeLinkViewPtr);
110+
nativeClear(nativePointer);
93111
}
94112

95113
public long size() {
96-
return nativeSize(nativeLinkViewPtr);
114+
return nativeSize(nativePointer);
97115
}
98116

99117
public boolean isEmpty() {
100-
return nativeIsEmpty(nativeLinkViewPtr);
118+
return nativeIsEmpty(nativePointer);
101119
}
102120

103121
public TableQuery where() {
104122
// Execute the disposal of abandoned realm objects each time a new realm object is created
105123
this.context.executeDelayedDisposal();
106-
long nativeQueryPtr = nativeWhere(nativeLinkViewPtr);
124+
long nativeQueryPtr = nativeWhere(nativePointer);
107125
try {
108126
return new TableQuery(this.context, this.parent, nativeQueryPtr);
109127
} catch (RuntimeException e) {
@@ -113,7 +131,7 @@ public TableQuery where() {
113131
}
114132

115133
public boolean isAttached() {
116-
return nativeIsAttached(nativeLinkViewPtr);
134+
return nativeIsAttached(nativePointer);
117135
}
118136

119137
/**
@@ -129,7 +147,7 @@ private void checkImmutable() {
129147
}
130148
}
131149

132-
protected static native void nativeClose(long nativeLinkViewPtr);
150+
private static native void nativeClose(long nativeLinkViewPtr);
133151
native long nativeGetRow(long nativeLinkViewPtr, long pos);
134152
private native long nativeGetTargetRowIndex(long nativeLinkViewPtr, long pos);
135153
private native void nativeAdd(long nativeLinkViewPtr, long rowIndex);

realm/realm-library/src/main/java/io/realm/internal/NativeObjectReference.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,25 @@
2222
* This class is used for holding the reference to the native pointers present in NativeObjects.
2323
* This is required as phantom references cannot access the original objects for this value.
2424
*/
25-
public class NativeObjectReference extends PhantomReference<NativeObject> {
25+
public abstract class NativeObjectReference extends PhantomReference<NativeObject> {
2626

2727
// The pointer to the native object to be handled
28-
final long nativePointer;
28+
protected final long nativePointer;
2929

3030
public NativeObjectReference(NativeObject referent, ReferenceQueue<? super NativeObject> referenceQueue) {
3131
super(referent, referenceQueue);
3232
nativePointer = referent.nativePointer;
3333
}
34+
35+
/**
36+
* This method is called when this reference gets cleared.
37+
* Subclasses should implement this method to dealloc the native pointer.
38+
*/
39+
protected abstract void cleanup();
40+
41+
@Override
42+
public void clear() {
43+
cleanup();
44+
super.clear();
45+
}
3446
}

realm/realm-library/src/main/java/io/realm/internal/UncheckedRow.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.realm.internal;
1818

19+
import java.lang.ref.ReferenceQueue;
1920
import java.util.Date;
2021

2122
/**
@@ -29,6 +30,18 @@
2930
*/
3031
public class UncheckedRow extends NativeObject implements Row {
3132

33+
protected static class UncheckedRowNativeObjectReference extends NativeObjectReference {
34+
public UncheckedRowNativeObjectReference(NativeObject referent,
35+
ReferenceQueue<? super NativeObject> referenceQueue) {
36+
super(referent, referenceQueue);
37+
}
38+
39+
@Override
40+
protected void cleanup() {
41+
nativeClose(nativePointer);
42+
}
43+
}
44+
3245
final Context context; // This is only kept because for now it's needed by the constructor of LinkView
3346
final Table parent;
3447

@@ -37,7 +50,7 @@ protected UncheckedRow(Context context, Table parent, long nativePtr) {
3750
this.parent = parent;
3851
this.nativePointer = nativePtr;
3952

40-
context.cleanRows();
53+
context.cleanNativeReferences();
4154
}
4255

4356
/**
@@ -51,7 +64,8 @@ protected UncheckedRow(Context context, Table parent, long nativePtr) {
5164
public static UncheckedRow getByRowIndex(Context context, Table table, long index) {
5265
long nativeRowPointer = table.nativeGetRowPtr(table.nativePtr, index);
5366
UncheckedRow row = new UncheckedRow(context, table, nativeRowPointer);
54-
context.rowReferences.put(new NativeObjectReference(row, context.referenceQueue), Context.ROW_REFERENCES_VALUE);
67+
context.rowReferences.put(new UncheckedRowNativeObjectReference(row, context.referenceQueue),
68+
Context.NATIVE_REFERENCES_VALUE);
5569
return row;
5670
}
5771

@@ -65,7 +79,8 @@ public static UncheckedRow getByRowIndex(Context context, Table table, long inde
6579
*/
6680
public static UncheckedRow getByRowPointer(Context context, Table table, long nativeRowPointer) {
6781
UncheckedRow row = new UncheckedRow(context, table, nativeRowPointer);
68-
context.rowReferences.put(new NativeObjectReference(row, context.referenceQueue), Context.ROW_REFERENCES_VALUE);
82+
context.rowReferences.put(new UncheckedRowNativeObjectReference(row, context.referenceQueue),
83+
Context.NATIVE_REFERENCES_VALUE);
6984
return row;
7085
}
7186

@@ -78,9 +93,11 @@ public static UncheckedRow getByRowPointer(Context context, Table table, long na
7893
* @return an instance of Row for the LinkView and index specified.
7994
*/
8095
public static UncheckedRow getByRowIndex(Context context, LinkView linkView, long index) {
81-
long nativeRowPointer = linkView.nativeGetRow(linkView.nativeLinkViewPtr, index);
82-
UncheckedRow row = new UncheckedRow(context, linkView.parent.getLinkTarget(linkView.columnIndexInParent), nativeRowPointer);
83-
context.rowReferences.put(new NativeObjectReference(row, context.referenceQueue), context.ROW_REFERENCES_VALUE);
96+
long nativeRowPointer = linkView.nativeGetRow(linkView.nativePointer, index);
97+
UncheckedRow row = new UncheckedRow(context, linkView.parent.getLinkTarget(linkView.columnIndexInParent),
98+
nativeRowPointer);
99+
context.rowReferences.put(new UncheckedRowNativeObjectReference(row, context.referenceQueue),
100+
Context.NATIVE_REFERENCES_VALUE);
84101
return row;
85102
}
86103

@@ -308,7 +325,7 @@ public boolean hasColumn(String fieldName) {
308325
protected native void nativeSetMixed(long nativeRowPtr, long columnIndex, Mixed data);
309326
protected native void nativeSetLink(long nativeRowPtr, long columnIndex, long value);
310327
protected native void nativeNullifyLink(long nativeRowPtr, long columnIndex);
311-
protected static native void nativeClose(long nativeRowPtr);
328+
private static native void nativeClose(long nativeRowPtr);
312329
protected native boolean nativeIsAttached(long nativeRowPtr);
313330
protected native boolean nativeHasColumn(long nativeRowPtr, String columnName);
314331
protected native boolean nativeIsNull(long nativeRowPtr, long columnIndex);

0 commit comments

Comments
 (0)