After some days of very difficult debugging for the issue "Codename One App killed on iOS only (by jetsam reason per-process-limit), randomly", I found that the main cause of the memory problem is PropertyIndex().loadJSON(...), that in my actual app allocates about ~50.000 instances of a specific PBO, while the necessary number of instances is about ~500. Of course I discovered this bug when I started to count the instances. I have an huge number of PBOs, so this bug is multiplied for all my PBOs... that's why the app exceeds mem limit and crashes on iOS.
I prepared an easy test case with three PBOs.
On the first execution, these PBOs are created and saved to JSON. This is the log:
[EDT] 0:0:0,82 - This is the first execution of the app (no JSON saved)
[EDT] 0:0:0,168 - 1 PBO1 instances
[EDT] 0:0:0,168 - 20 PBO2 instances
[EDT] 0:0:0,168 - 80 PBO3 instances
[EDT] 0:0:0,207 - JSON saved, kill the app and open it again
On the second execution, this is the log:
[EDT] 0:0:0,78 - This is not the first execution of the app, we are going to retrieve data from JSON
[EDT] 0:0:0,190 - 1 PBO1 instances
[EDT] 0:0:0,190 - 400 PBO2 instances
[EDT] 0:0:0,190 - 3200 PBO3 instances
As you can see, in this test case we have 400 instances instead of 20 and 3200 instances instead of 80.
I attach a zip file with the test case made using Netbeans 10 and Java 8:
TestCasePBO_JSON.zip
This is the code:
MainClass
Form hi = new Form("Test Case PBOs to JSON", BoxLayout.y());
hi.add(new Label("Test Case PBOs to JSON, see logs"));
hi.show();
if (!Storage.getInstance().exists("myJSON")) {
Log.p("This is the first execution of the app (no JSON saved)");
// I create "1" instance of PBO1
PBO1 pbo1 = new PBO1();
// I add "20" PBO2 items to the PBO1 map
for (int i = 0; i < 20; i++) {
pbo1.map.put(i + "", new PBO2());
}
// Note that each PBO2 instance has a list with "4" PBO3 instances,
// so now we should have:
// "1" PBO1 instance
// "20" PBO2 instances
// "80" PBO3 instances
// Let's discover if it's true:
Log.p(PBO1.getCounter() + " PBO1 instances");
Log.p(PBO2.getCounter() + " PBO2 instances");
Log.p(PBO3.getCounter() + " PBO3 instances");
// Now we save PBO1 to JSON, PBO2 and PBO3 are implicitly included in the saving
pbo1.getPropertyIndex().storeJSON("myJSON");
Log.p("JSON saved, kill the app and open it again");
} else {
Log.p("This is not the first execution of the app, we are going to retrieve data from JSON");
PBO1 pbo1 = new PBO1();
pbo1.getPropertyIndex().loadJSON("myJSON");
Log.p(PBO1.getCounter() + " PBO1 instances");
Log.p(PBO2.getCounter() + " PBO2 instances");
Log.p(PBO3.getCounter() + " PBO3 instances");
}
PBO1.java
public class PBO1 implements PropertyBusinessObject {
private static int counter = 0;
public final Property<String, PBO1> name = new Property<>("name", "MyNameIsPBO1");
public final MapProperty<String, PBO2, PBO1> map = new MapProperty<>("myMap", String.class, PBO2.class);
private final PropertyIndex idx = new PropertyIndex(this, "PBO1",
name, map);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
public PBO1() {
counter++;
}
public static int getCounter() {
return counter;
}
}
PBO2.java
public class PBO2 implements PropertyBusinessObject {
private static int counter = 0;
public final Property<String, PBO2> name = new Property<>("name", "MyNameIsPBO2");
public final ListProperty<PBO3, PBO2> list = new ListProperty<>("myList", PBO3.class, new PBO3(), new PBO3(), new PBO3(), new PBO3());
private final PropertyIndex idx = new PropertyIndex(this, "PBO2",
name, list);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
public PBO2() {
counter++;
}
public static int getCounter() {
return counter;
}
}
PBO3.java
public class PBO3 implements PropertyBusinessObject {
private static int counter = 0;
public final Property<String, PBO3> name = new Property<>("name", "MyNameIsPBO3");
private final PropertyIndex idx = new PropertyIndex(this, "PBO3",
name);
@Override
public PropertyIndex getPropertyIndex() {
return idx;
}
public PBO3() {
counter++;
}
public static int getCounter() {
return counter;
}
}
After some days of very difficult debugging for the issue "Codename One App killed on iOS only (by jetsam reason per-process-limit), randomly", I found that the main cause of the memory problem is
PropertyIndex().loadJSON(...), that in my actual app allocates about ~50.000 instances of a specific PBO, while the necessary number of instances is about ~500. Of course I discovered this bug when I started to count the instances. I have an huge number of PBOs, so this bug is multiplied for all my PBOs... that's why the app exceeds mem limit and crashes on iOS.I prepared an easy test case with three PBOs.
On the first execution, these PBOs are created and saved to JSON. This is the log:
On the second execution, this is the log:
As you can see, in this test case we have 400 instances instead of 20 and 3200 instances instead of 80.
I attach a zip file with the test case made using Netbeans 10 and Java 8:
TestCasePBO_JSON.zip
This is the code:
MainClass
PBO1.java
PBO2.java
PBO3.java