Garbage collection
Background history: Finalize in Java, when an object was no longer referenced, it was garbage collected, and the finalize method called, so the object could clean up anything the garbage collector doesn't know about, e.g. memory in a graphics heap, external network connections, locked resources, etc. A very useful function. A very *essential* function for a garbage collected / reference counted environment.
2010...2018, the garbage collector gets lazier and lazier, finalize cannot be relied up, it gets called less and less and later and later.
2018 Finalize gets deprecated, without a suitable replacement. Various half assed alternatives are promoted, which basically amount to the class re-creating a separate reference count and garbage collect, and forcing explicit calls to 'free' or 'close' the external resources associated with the object. Why have garbage collection then, if you need your own reference counting?? Give the class a 'FreeMeNow' destructor then if you're not prepared to fix Finalize, so at least we have one consistent reference count!!
So, now you allocate a block and *maybe* it will be freed and maybe not, so how do you check for memory leaks? Well there is a sort of way using WeakReferences. A WeakReference doesn't count as object-usage, and it goes to null when the garbage collector determines the object is no longer used by anything excepting weak references. So you could track the objects that should be freed till their weakreferences become null and you know they've been garbage collected. Bingo, it involves some polling, but there is a way.
Something like TrackLeaks(new WeakReference<String>(s), "Tracking s at line 2938");
So Android 8 comes and goes and this works, within 2 minutes blocks that should be freed, are freed. Leaks are found and leaks are fixed as before.
Now we get to Android 10, and I see blocks that hang around for hours. Let me give you some code so you can see how bizarre it is.
{
StringBuilder sb = new StringBuilder();
sb.add("stuff");
sb.add("stuff and more stuff");
String s1 = sb.toString();
TrackLeaks(new WeakReference<String>(s1), "S1Leak");
// lets clone the string to show how annoying this is
String s2 = new String(s1);
TrackLeaks(new WeakReference<String>(s2), "S2Leak");
return s2;
}
So, I've returned s2, and perhaps it leaks? No.... this code has been used for years, it does not leak, what's new is s1 leaks, not s2.
It's like the garbage collector is doing ONE pass, very very rarely, and finds s1 is not suitable for garbage collection because it is used by s2*. It then determines that s2 can be garbage collected and stops. Done. Except it isn't, s1 can now be
I see its a "generational heap" now in Android 10, I suspect that because s1 stays around, it is moved to the long term heap where it will hardly ever be examined for garbage collection!
I'm not sure if that's the cause, but having spent days on it, code that happily ran on Android 8, now hits the 512Mb limit on Android 10 and crashes, I am at a loss to explain these weird cases, that's my best guess.
Honestly, I'm sick of it. They have ONE JOB, manage a 8GB RAM heap for software that can happily run in 800Mb, and forces itself to run in 400Mb because of a crappy heap limit. I get max 512MB and a crappy unreliable garbage collector to work with. You give me an unstable, poorly designed OS that neither virtualizes the hardware like it should, nor delivers consistency behavior across versions like it should.
* Yes this is correct, I know the documentation says it always takes a copy, but new String(s1) takes the *value* if the string's length shows it is not a substring, so s2 would, I assume, be referencing s1 in the above case. See the code of the String constructor.