Out of memory errors in Java API that uses finalizers in order to free memory allocated by C calls
Asked Answered
D

2

1

We have a Java API that is a wrapper around a C API. As such, we end up with several Java classes that are wrappers around C++ classes.

These classes implement the finalize method in order to free the memory that has been allocated for them.

Generally, this works fine. However, in high-load scenarios we get out of memory exceptions. Memory dumps indicate that virtually all the memory (around 6Gb in this case) is filled with the finalizer queue and the objects waiting to be finalized.

For comparison, the C API on its own never goes over around 150 Mb of memory usage.

Under low load, the Java implementation can run indefinitely. So this doesn't seem to be a memory leak as such. It just seem to be that under high load, new objects that require finalizing are generated faster than finalizers get executed.

Obviously, the 'correct' fix is to reduce the number of objects being created. However, that's a significant undertaking and will take a while. In the meantime, is there a mechanism that might help alleviate this issue? For example, by giving the GC more resources.

Davies answered 8/10, 2020 at 14:33 Comment(3)
That’s like asking, how thick the pillow between your gun and knee should be. Creating Java wrappers around things implemented in C++ is not bad, but having a one-to-one correspondence between Java classes and C++ classes is combining the worst of both worlds.Mercedes
@Mercedes Yes, I understand that, but it is what it is. I guess I was hoping someone might have a kevlar pillow that I could use while I move my knee to a safer place. :)Davies
Using direct ByteBuffer may reduce the problems, as the implementation keeps track of the amount of allocated native memory and is capable of slowing down new allocations until potential reclaimable buffers have been processed. Of course, that’s all implementation dependent.Mercedes
L
3

Java was designed around the idea that finalizers could be used as the primary cleanup mechanism for objects that go out of scope. Such an approach may have been almost workable when the total number of objects was small enough that the overhead of an "always scan everything" garbage collector would have been acceptable, but there are relatively few cases where finalization would be appropriate cleanup measure in a system with a generational garbage collector (which nearly all JVM implementations are going to have, because it offers a huge speed boost compared to always scanning everything).

Using Closable along with a try-with-resources constructs is a vastly superior approach whenever it's workable. There is no guarantee that finalize methods will get called with any degree of timeliness, and there are many situations where patterns of interrelated objects may prevent them from getting called at all. While finalize can be useful for some purposes, such as identifying objects which got improperly abandoned while holding resources, there are relatively few purposes for which it would be the proper tool.

If you do need to use finalizers, you should understand an important principle: contrary to popular belief, finalizers do not trigger when an object is actually garbage collected"--they fire when an object would have been garbage collected but for the existence of a finalizer somewhere [including, but not limited to, the object's own finalizer]. No object can actually be garbage collected while any reference to it exists in any local variable, in any other object to which any reference exists, or any object with a finalizer that hasn't run to completion. Further, to avoid having to examine all objects on every garbage-collection cycle, objects which have been alive for awhile will be given a "free pass" on most GC cycles. Thus, if an object with a finalizer is alive for awhile before it is abandoned, it may take quite awhile for its finalizer to run, and it will keep objects to which it holds references around long enough that they're likely to also earn a "free pass".

I would thus suggest that to the extent possible, even when it's necessary to use finalizer, you should limit their use to privately-held objects which in turn avoid holding strong references to anything which isn't explicitly needed for their cleanup task.

Larue answered 8/10, 2020 at 15:42 Comment(6)
Finalizers were never intended to be “the primary cleanup mechanism”. I/O classes had close() methods from day one. Likewise, windows have a dispose() method since Java 1.0. Finalization always was the last resort mechanism, not the primary one. And “objects that go out of scope” ins misleading. Names go out of scope. Objects may become unreachable, which is only loosely connected to variable scopes.Mercedes
@Holger: I/O classes had to deal with the need to close and reopen a particular file immediately, which would have been impossible with finalization, but I am unaware of the concept of "close" having been applied to classes that tie up finite but fungible resources.Larue
Which kind of resources and classes do you have in mind? As far as I can see, there are only resources that should be closed explicitly and objects just consisting of memory that the garbage collector will handle. If there is a 3rd category, having finalizers as the primary cleanup mechanism, I must have missed those actual use cases.Mercedes
@Holger: Things like GDI handles, as would any other kind of object that needs to work with non-relocatable buffers in the underlying environment. I'm not really familiar with the Java graphics and windowing abstractions, but in some graphic environments, asking the environment to create a brush and then using it for many graphics operations can be much faster than asking the environment to create a brush for each operation. Likewise, loading an image into an bitmap that's kept in environment-specific fashion and drawing it repeatedly may be much faster...Larue
...than having to build image in the environment's graphics format every time one wants to draw it.Larue
Nobody doubts that using native resources may speed up graphics operations. Still, these resource must be released, preferably in a controlled way, not via unreliable finalization. I already mentioned Window.dispose() in my first comment. There’s also Graphics.dispose(), etc. You still didn’t name an example for which “finalizers could be used as the primary cleanup mechanism”.Mercedes
S
2

Phantom references is an alternative to finalizers available in Java.

Phantom references allow you to better control resource reclamation process.

  • you can combine explicit resource disposal (e.g. try with resources construct) with GC base disposal
  • you can employ multiple threads for postmortem housekeeping

Using phantom references is complicated tough. In this article you can find a minimal example of phantom reference base resource housekeeping.

In modern Java there are also Cleaner class which is based on phantom reference too, but provides infrastructure (reference queue, worker threads etc) for ease of use.

Slavish answered 8/10, 2020 at 22:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.