Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

performance-boost and fix of memory-issues: Get rid of Object.finalize() #1363

Closed
fab1an opened this issue Jul 14, 2016 · 17 comments
Closed

Comments

@fab1an
Copy link

fab1an commented Jul 14, 2016

Here (facebook/react-native#8711) you can find a lengthy explanation why Object.finalize() is bad, but the two main reasons, why Fresco should stop using it are:

  1. The FinalizerQueue can fill-up and stop being processed, this can happen for many reasons, and it's possible that Object.finalize() is never called until the JVM exits. This also means that, every object that's referenced from an object whose finalizer isn't called, isn't garbage-colllected. So even finalizers that log only can lead to memory issues.

  2. Finalizers slow everything down by a giant amount. Quote from effective java:

    Oh, and one more thing: there is a severe performance penalty for using finalizers. On my machine, the time to create and destroy a simple object is about 5.6 ns. Adding a finalizer increases the time to 2,400 ns. In other words, it is about 430 times slower to create and destroy objects with finalizers.

I know it's tedious to release everything manually, but doing it will give Fresco and depending projects an extreme performance-boost and lower memory-footprint. This will help everyone.

Finalizers were just a bad idea which should have never happened.

@dbrant
Copy link

dbrant commented Jul 15, 2016

A huge +1 for this. Our app uses Fresco extensively, and I've been seeing a lot of FinalizerReference instances that don't get collected, and can lead to runaway memory usage. Really looking forward to seeing this done!

@balazsbalazs
Copy link
Contributor

While I see your point that having finalizers adds a marginal perf penalty I don't think that it's the major cause for the memory issues here.
Fresco uses deterministic memory management so if used correctly all the memory is freed up before you get to the finalizer.
If it not used correctly (which being an open source library we can't guarantee) we're using the finalizers to clean up the memory. The reason we're doing this is because we're using the ashmem, which is a shared memory - so if you hold on to memory you might degrade not just your apps but the phones experience.

We're going to take another look at the perf penalty and the reason why you see so many FinalizerReferences - but I really don't think that's the cause for high memory usage.

Both Facebook and Twitter are using Fresco - so if there would be any regression we'd see that in our graphs very-very early and it would effect us in a major way.

@fab1an
Copy link
Author

fab1an commented Jul 19, 2016

@balazsbalazs Except in the case of animated images, right?

@fab1an
Copy link
Author

fab1an commented Jul 19, 2016

@balazsbalazs And finalizers add more than a "marginal perf penalty". It's orders of magnitude. Just instantiation takes 3 times as long (test here: facebook/react-native#8780 (comment) ), if you add the multiple GC cycles finalizable objects need, you get much larger slowdowns ( 2400ns instead of 6ns here: http://www.informit.com/articles/article.aspx?p=1216151&seqNum=7 ).

@fab1an
Copy link
Author

fab1an commented Jul 19, 2016

It's simple: Fresco is wasting precious resources by using an obsolete technique that is discouraged since years for not one, but about 20 different reasons.

What you are doing is muddling your memory-management with that of every other library running in the same JVM.

You are throwing away years of GC optimization as well.

@balazsbalazs
Copy link
Contributor

@fab1an:
As I said before: "We're going to take another look at the perf penalty and the reason why the FinalizerReferences are not collected - but I really don't think that's the cause for high memory usage."

As with every engineering decision there's a tradeoff here:

  • on one hand you can gain CPU cycles
  • on the other hand you're risking leaking huge portion of shared memory

The question is how do you evaluate this tradeoff: what is exactly the perf penalty and how much you can loose if you leak a bitmap.

Example:
According to your measurements an object initialization takes 4.6us instead of 1.7us. Although that is 3x, you're not likely to have more than 100 objects on a screen at once, which is still 0.46ms instead of 0.17ms - which even in this extreme case would be a small portion of the frame (16ms) so it would not result in a skipped frame.

On the other hand if you forget to close a CloseableReference - which can happen very easily, since deterministic memory management is very alien for Java - you can leak a a few MB of Android Shared memory easily. On low end phones memory is much more valuable than CPU cycles.


Now here the real cost is the GC for the finalizers - and since Android is not Java we need to measure the penalty on both Dalvik and ART GC to understand what does that really mean to make that tradeoff.

Getting rid of the finalizers introduces the risk of leaking huge amount of memory - and since this is an open source library and we can't control the callers + deterministic memory management is an unusual thing in Java this is a real risk.

@balazsbalazs
Copy link
Contributor

@fab1an : you've already done some great analysis on this, I'd be happy if you'd help us analyze the cost of the GC penalty for Finalizers on Dalvik and ART. (A bit deeper than there's a second pass GC and object references on the heap).

@balazsbalazs
Copy link
Contributor

Oh, and yes, you're right: we need to fix the animated images

@fab1an
Copy link
Author

fab1an commented Jul 20, 2016

@balazsbalazs I know why you are using finalizers, I have read the code, that's why I already posted in facebook/react-native#8711:

"Finalization might seem to be a good mechanism for making sure that resources do not go undisposed, [..] an alternate fallback mechanism, a "second chance" safeguard which will automagically save the day by disposing of the resources that they forgot. This is dead wrong."

  1. What you achieve is a mechanism that is sometimes able to check if a user forgot to close a resource.
    If your app has enough HeapSize, but you are out of native memory then your finalizers won't run to free native resources or warn users and there's nothing you can do about that.
    So exactly the case you want to warn about, is the case where finalizers won't be able to help you.
  2. What you achieve 100% of the time is a slowdown of the entire VM your code runs in.

As a last comment, I'm quoting Wikipedia (https://en.wikipedia.org/wiki/Finalizer#Problems)

"Finalizers can cause a significant number of problems, and are thus strongly discouraged by a number of authorities.

  • Finalizers may not be called in a timely manner, or at all, so they cannot be relied upon to persist state, release scarce resources, or do anything else of importance.
  • Finalizers are run based on garbage collection, which is generally based on managed memory pressure – they are not run in case of other resource scarcity, and thus are not suited for managing other scarce resources.
  • Slow finalizers may delay other finalizers.
  • Finalizers may cause synchronization issue, even in otherwise sequential (single-threaded) programs, as finalization may be done concurrently (concretely, in one or more separate
    threads).
  • ...

I won't spend more time on profiling what essentially are vendor-dependent race-conditions.

I consider it a waste of development time to reproduce mistakes that thousands of other have done and written about before.

I'll probably just switch to Picasso from square for now, since I mostly have static images anyway.

@fab1an
Copy link
Author

fab1an commented Jul 20, 2016

@balazsbalazs And if you look into why the queue stops being processed, there seem to be more people having that problem: https://stackoverflow.com/questions/37001181/android-finalizerdaemon-hanging-up

I reported a bug with google, but got no answer so far: https://code.google.com/p/android/issues/detail?id=215906

@silverspace
Copy link

This is also an issue on react-native-animatable with animations that loop:
oblador/react-native-animatable#43

@itinance
Copy link

Any news on this?

@brianhama
Copy link

This seems to be a huge issue with Fresco. We just switched to Fresco from Picasso and now our memory heap looks like this:

capture

We weren't seeing anything like this before in terms of the number of finalizers. Our analytics are showing a lot of OOMs. I won't pretend to understand the issue anywhere near as well as fab1an, but I can verify that the issue does seem to be having a very negative impact.

@fab1an
Copy link
Author

fab1an commented Jan 23, 2017

@brianhama Can you post a screenshot of the memory heap using picasso before the switch? That would be very informative and help this issue along.

@foghina
Copy link
Contributor

foghina commented May 8, 2017

@brianhama that is a red herring: the vm will create a FinalizerReference for each object that has a finalize() method. What you're looking at is not necessarily the queue of objects waiting to be finalized, but merely the list of all object instances implementing finalize(). The retained size is obviously huge, as it will include all the images that are on screen or in the bitmap cache.

Relating to the finalizers issue: we've run an experiment over a couple of months where we provide a separate implementation of CloseableReference that does not implement finalize(), but uses PhantomReferences to clean up the native memory. In the millions of devices that have participated in the experiment, we have not seen statistically significant improvements in:

  • crashes in general
  • out of memory crashes
  • scroll performance
  • cpu usage
  • battery drain

In conclusion, we will be sticking with finalizers for now.

@foghina foghina closed this as completed May 8, 2017
@fab1an
Copy link
Author

fab1an commented May 8, 2017

@foghina Great that you got around to analyse this. Could you publish the data, as that's really interesting evidence about the JVM and ART!

@foghina
Copy link
Contributor

foghina commented May 8, 2017

There's not much more data than "none of our metrics changed" :), and if there were, I doubt I could post it. Just to clarify, this is not to say that finalizers are good, but that alternative options such as PhantomReferences are not necessarily better. And we're not in a place where we can live without a solution for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants