Skip to content

Commit 577bde9

Browse files
authored
Fix mem leak in polygonToCells and do some micro optimization (#150)
1 parent 4e2f6b0 commit 577bde9

File tree

3 files changed

+121
-82
lines changed

3 files changed

+121
-82
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@
233233
<plugin>
234234
<groupId>org.jacoco</groupId>
235235
<artifactId>jacoco-maven-plugin</artifactId>
236-
<version>0.8.7</version>
236+
<version>0.8.12</version>
237237
<executions>
238238
<execution>
239239
<id>jacoco-instrument</id>

src/main/c/h3-java/src/jniapi.c

Lines changed: 103 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,63 @@
3333
return; \
3434
}
3535

36+
static jclass java_lang_OutOfMemoryError;
37+
static jclass com_uber_h3core_exceptions_H3Exception;
38+
39+
static jmethodID com_uber_h3core_exceptions_H3Exception_init;
40+
static jmethodID java_lang_OutOfMemoryError_init;
41+
42+
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
43+
JNIEnv *env;
44+
if ((**vm).GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
45+
return JNI_ERR;
46+
} else {
47+
jclass local_h3eClass =
48+
(**env).FindClass(env, "com/uber/h3core/exceptions/H3Exception");
49+
com_uber_h3core_exceptions_H3Exception_init =
50+
(**env).GetMethodID(env, local_h3eClass, "<init>", "(I)V");
51+
com_uber_h3core_exceptions_H3Exception =
52+
(jclass)(**env).NewGlobalRef(env, local_h3eClass);
53+
54+
jclass local_oomeClass =
55+
(**env).FindClass(env, "java/lang/OutOfMemoryError");
56+
java_lang_OutOfMemoryError_init =
57+
(**env).GetMethodID(env, local_oomeClass, "<init>", "()V");
58+
java_lang_OutOfMemoryError =
59+
(jclass)(**env).NewGlobalRef(env, local_oomeClass);
60+
61+
return JNI_VERSION_1_6;
62+
}
63+
}
64+
65+
void JNI_OnUnload(JavaVM *vm, void *reserved) {
66+
JNIEnv *env;
67+
if ((**vm).GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
68+
// Something is wrong but nothing we can do about this :(
69+
return;
70+
} else {
71+
// delete global references so the GC can collect them
72+
if (com_uber_h3core_exceptions_H3Exception != NULL) {
73+
(**env).DeleteGlobalRef(env,
74+
com_uber_h3core_exceptions_H3Exception);
75+
}
76+
if (java_lang_OutOfMemoryError != NULL) {
77+
(**env).DeleteGlobalRef(env, java_lang_OutOfMemoryError);
78+
}
79+
}
80+
}
81+
3682
/**
3783
* Triggers an H3Exception
3884
*/
3985
void ThrowH3Exception(JNIEnv *env, H3Error err) {
40-
jclass h3e =
41-
(**env).FindClass(env, "com/uber/h3core/exceptions/H3Exception");
42-
43-
if (h3e != NULL) {
44-
jmethodID h3eConstructor =
45-
(**env).GetMethodID(env, h3e, "<init>", "(I)V");
46-
47-
if (h3eConstructor != NULL) {
48-
jthrowable h3eInstance =
49-
(jthrowable)((**env).NewObject(env, h3e, h3eConstructor, err));
86+
jthrowable h3eInstance = (jthrowable)((**env).NewObject(
87+
env, com_uber_h3core_exceptions_H3Exception,
88+
com_uber_h3core_exceptions_H3Exception_init, err));
5089

51-
if (h3eInstance != NULL) {
52-
// TODO: Is ExceptionClear needed here?
53-
(**env).Throw(env, h3eInstance);
54-
}
55-
}
90+
if (h3eInstance != NULL) {
91+
(**env).Throw(env, h3eInstance);
92+
(**env).DeleteLocalRef(env, h3eInstance);
5693
}
5794
}
5895

@@ -65,21 +102,13 @@ void ThrowH3Exception(JNIEnv *env, H3Error err) {
65102
void ThrowOutOfMemoryError(JNIEnv *env) {
66103
// Alternately, we could call the JNI function FatalError(JNIEnv *env, const
67104
// char *msg)
68-
jclass oome = (**env).FindClass(env, "java/lang/OutOfMemoryError");
69-
70-
if (oome != NULL) {
71-
jmethodID oomeConstructor =
72-
(**env).GetMethodID(env, oome, "<init>", "()V");
73-
74-
if (oomeConstructor != NULL) {
75-
jthrowable oomeInstance =
76-
(jthrowable)((**env).NewObject(env, oome, oomeConstructor));
105+
jthrowable oomeInstance = (jthrowable)((**env).NewObject(
106+
env, java_lang_OutOfMemoryError, java_lang_OutOfMemoryError_init));
77107

78-
if (oomeInstance != NULL) {
79-
(**env).ExceptionClear(env);
80-
(**env).Throw(env, oomeInstance);
81-
}
82-
}
108+
if (oomeInstance != NULL) {
109+
(**env).ExceptionClear(env);
110+
(**env).Throw(env, oomeInstance);
111+
(**env).DeleteLocalRef(env, oomeInstance);
83112
}
84113
}
85114

@@ -96,43 +125,50 @@ H3Error CreateGeoPolygon(JNIEnv *env, jdoubleArray verts, jintArray holeSizes,
96125
if (polygon->geoloop.verts != NULL) {
97126
polygon->numHoles = (**env).GetArrayLength(env, holeSizes);
98127

99-
polygon->holes = calloc(sizeof(GeoPolygon), polygon->numHoles);
100-
if (polygon->holes == NULL) {
101-
ThrowOutOfMemoryError(env);
102-
return E_MEMORY_ALLOC;
103-
}
128+
if (polygon->numHoles > 0) {
129+
polygon->holes = calloc(polygon->numHoles, sizeof(GeoPolygon));
130+
if (polygon->holes == NULL) {
131+
(**env).ReleaseDoubleArrayElements(
132+
env, verts, polygon->geoloop.verts, JNI_ABORT);
133+
ThrowOutOfMemoryError(env);
134+
return E_MEMORY_ALLOC;
135+
}
104136

105-
jint *holeSizesElements =
106-
(**env).GetIntArrayElements(env, holeSizes, 0);
107-
if (holeSizesElements == NULL) {
108-
free(polygon->holes);
109-
ThrowOutOfMemoryError(env);
110-
return E_MEMORY_ALLOC;
111-
}
137+
jint *holeSizesElements =
138+
(**env).GetIntArrayElements(env, holeSizes, 0);
139+
if (holeSizesElements == NULL) {
140+
(**env).ReleaseDoubleArrayElements(
141+
env, verts, polygon->geoloop.verts, JNI_ABORT);
142+
free(polygon->holes);
143+
ThrowOutOfMemoryError(env);
144+
return E_MEMORY_ALLOC;
145+
}
112146

113-
jdouble *holeVertsElements =
114-
(**env).GetDoubleArrayElements(env, holeVerts, 0);
115-
if (holeVertsElements == NULL) {
116-
free(polygon->holes);
117-
(**env).ReleaseIntArrayElements(env, holeSizes, holeSizesElements,
118-
0);
119-
ThrowOutOfMemoryError(env);
120-
return E_MEMORY_ALLOC;
121-
}
147+
jdouble *holeVertsElements =
148+
(**env).GetDoubleArrayElements(env, holeVerts, 0);
149+
if (holeVertsElements == NULL) {
150+
(**env).ReleaseDoubleArrayElements(
151+
env, verts, polygon->geoloop.verts, JNI_ABORT);
152+
free(polygon->holes);
153+
(**env).ReleaseIntArrayElements(env, holeSizes,
154+
holeSizesElements, JNI_ABORT);
155+
ThrowOutOfMemoryError(env);
156+
return E_MEMORY_ALLOC;
157+
}
122158

123-
size_t offset = 0;
124-
for (int i = 0; i < polygon->numHoles; i++) {
125-
// This is the number of doubles, so convert to number of verts
126-
polygon->holes[i].numVerts = holeSizesElements[i] / 2;
127-
polygon->holes[i].verts = holeVertsElements + offset;
128-
offset += holeSizesElements[i];
159+
size_t offset = 0;
160+
for (int i = 0; i < polygon->numHoles; i++) {
161+
// This is the number of doubles, so convert to number of verts
162+
polygon->holes[i].numVerts = holeSizesElements[i] / 2;
163+
polygon->holes[i].verts = holeVertsElements + offset;
164+
offset += holeSizesElements[i];
165+
}
166+
(**env).ReleaseIntArrayElements(env, holeSizes, holeSizesElements,
167+
JNI_ABORT);
168+
// holeVertsElements is not released here because it is still being
169+
// pointed to by polygon->holes[*].verts. It will be released in
170+
// DestroyGeoPolygon.
129171
}
130-
131-
(**env).ReleaseIntArrayElements(env, holeSizes, holeSizesElements, 0);
132-
// holeVertsElements is not released here because it is still being
133-
// pointed to by polygon->holes[*].verts. It will be released in
134-
// DestroyGeoPolygon.
135-
136172
return E_SUCCESS;
137173
} else {
138174
ThrowOutOfMemoryError(env);
@@ -143,15 +179,16 @@ H3Error CreateGeoPolygon(JNIEnv *env, jdoubleArray verts, jintArray holeSizes,
143179
void DestroyGeoPolygon(JNIEnv *env, jdoubleArray verts,
144180
jintArray holeSizesElements, jdoubleArray holeVerts,
145181
GeoPolygon *polygon) {
146-
(**env).ReleaseDoubleArrayElements(env, verts, polygon->geoloop.verts, 0);
182+
(**env).ReleaseDoubleArrayElements(env, verts, polygon->geoloop.verts,
183+
JNI_ABORT);
147184

148185
if (polygon->numHoles > 0) {
149186
// The hole verts were pinned only once, so we don't need to iterate.
150187
(**env).ReleaseDoubleArrayElements(env, holeVerts,
151-
polygon->holes[0].verts, 0);
152-
}
188+
polygon->holes[0].verts, JNI_ABORT);
153189

154-
free(polygon->holes);
190+
free(polygon->holes);
191+
}
155192
}
156193

157194
/*
@@ -584,7 +621,6 @@ JNIEXPORT void JNICALL Java_com_uber_h3core_NativeMethods_polygonToCells(
584621
(**env).ReleaseLongArrayElements(env, results, resultsElements, 0);
585622
} else {
586623
ThrowOutOfMemoryError(env);
587-
return;
588624
}
589625

590626
DestroyGeoPolygon(env, verts, holeSizes, holeVerts, &polygon);

src/main/java/com/uber/h3core/H3Core.java

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -546,16 +546,18 @@ public List<Long> polygonToCells(List<LatLng> points, List<List<LatLng>> holes,
546546
int[] holeSizes = new int[0];
547547
double[] holeVerts = new double[0];
548548
if (holes != null) {
549-
holeSizes = new int[holes.size()];
549+
int holesSize = holes.size();
550+
holeSizes = new int[holesSize];
550551
int totalSize = 0;
551-
for (int i = 0; i < holes.size(); i++) {
552-
totalSize += holes.get(i).size() * 2;
552+
for (int i = 0; i < holesSize; i++) {
553+
int holeSize = holes.get(i).size() * 2;
554+
totalSize += holeSize;
553555
// Note we are storing the number of doubles
554-
holeSizes[i] = holes.get(i).size() * 2;
556+
holeSizes[i] = holeSize;
555557
}
556558
holeVerts = new double[totalSize];
557559
int offset = 0;
558-
for (int i = 0; i < holes.size(); i++) {
560+
for (int i = 0; i < holesSize; i++) {
559561
offset = packGeofenceVertices(holeVerts, holes.get(i), offset);
560562
}
561563
}
@@ -576,16 +578,19 @@ public List<Long> polygonToCells(List<LatLng> points, List<List<LatLng>> holes,
576578
* @return Next offset to begin filling from
577579
*/
578580
private static int packGeofenceVertices(double[] arr, List<LatLng> original, int offset) {
579-
assert arr.length >= (original.size() * 2) + offset;
581+
int newOffset = (original.size() * 2) + offset;
582+
assert arr.length >= newOffset;
580583

581-
for (int i = 0; i < original.size(); i++) {
584+
for (int i = 0, size = original.size(); i < size; i++) {
582585
LatLng coord = original.get(i);
583586

584-
arr[(i * 2) + offset] = toRadians(coord.lat);
585-
arr[(i * 2) + 1 + offset] = toRadians(coord.lng);
587+
int firstOffset = (i * 2) + offset;
588+
int secondOffset = firstOffset + 1;
589+
arr[firstOffset] = toRadians(coord.lat);
590+
arr[secondOffset] = toRadians(coord.lng);
586591
}
587592

588-
return (original.size() * 2) + offset;
593+
return newOffset;
589594
}
590595

591596
/** Create polygons from a set of contiguous indexes */
@@ -611,7 +616,7 @@ public List<List<List<LatLng>>> cellsToMultiPolygon(Collection<Long> h3, boolean
611616
for (List<LatLng> loop : loops) {
612617
// For each coordinate in the loop, we need to convert to degrees,
613618
// and ensure the correct ordering (whether geoJson or not.)
614-
for (int vectorInLoop = 0; vectorInLoop < loop.size(); vectorInLoop++) {
619+
for (int vectorInLoop = 0, size = loop.size(); vectorInLoop < size; vectorInLoop++) {
615620
final LatLng v = loop.get(vectorInLoop);
616621
final double origLat = toDegrees(v.lat);
617622
final double origLng = toDegrees(v.lng);
@@ -1173,9 +1178,7 @@ private List<String> h3ToStringList(Collection<Long> collection) {
11731178

11741179
/** Creates a new list with all non-zero elements of the array as members. */
11751180
private static List<Long> nonZeroLongArrayToList(long[] out) {
1176-
// Allocate for the case that we need to copy everything from
1177-
// the `out` array.
1178-
List<Long> ret = new ArrayList<>(out.length);
1181+
List<Long> ret = new ArrayList<>();
11791182

11801183
for (int i = 0; i < out.length; i++) {
11811184
long h = out[i];

0 commit comments

Comments
 (0)