4

I understand and enjoy the benefits of the Garbage Collection in Java. However I don't understand why there is no way in Java to explicitly (and quickly) destroy an object. Surely this could be useful in some cases, I assume performance-critical software.

It's true that in Java the GC will delete an object with no existing reference to it, so if I want an object deleted I can set the reference to it to null. But if I understand correctly, it isn't ensured that the GC will indeed delete the object, at least not immediately. And that's out of the programmer's control.

Why is there no way in Java to explicitly destroy objects?

While I understand that Java was designed to be used as a high-level language, that abstracts away some of the technical details from the programmer to make things easier: Java has become one of the most widely used languages, and is used in huge projects. I assume that in huge projects, performance is often an issue. Since Java had grown to become what it is, why wasn't explicit object destruction added to the language?

gnat
  • 21,442
  • 29
  • 112
  • 288
Aviv Cohn
  • 21,190
  • 31
  • 118
  • 178
  • Partially because Java was intended to be a high-level language that abstracted manual memory management, among other things, away from the programmer. Allowing explicit memory management would pretty much go against one of the reasons Java was made in the first place. – awksp May 21 '14 at 21:16
  • 1
    "Gosling did not get the significance of RAII at the time he designed Java. In his interviews he often talked about reasons for leaving out generics and operator overloading, but never mentioned deterministic destructors and RAII. Funny enough, **even Stroustrup wasn't aware of the importance of deterministic destructors at the time he designed them**..." ([possible duplicate](http://programmers.stackexchange.com/a/118310/31260)) – gnat May 21 '14 at 21:22
  • @gnat Then why wasn't deterministic destruction added later - especially since Java had grown to what it is today? – Aviv Cohn May 21 '14 at 21:25
  • @Prog You say that as if it's an easy thing to add. Once the language has been designed with garbage collection in mind, it gets pretty deeply routed into how things work. – KChaloux May 21 '14 at 21:28
  • 2
    this was asked and answered in [Why can't Java/C# implement RAII?](http://programmers.stackexchange.com/questions/216024/why-cant-java-c-implement-raii) and explored in some more details in [Disadvantages of scoped-based memory management](http://programmers.stackexchange.com/q/231789/31260) – gnat May 21 '14 at 21:28
  • 1
    @Prog - think about what would need to happen to get a 'destroy now' into Java - you'd need syntax for it, but you'd also need some way to make sure that the programmer didn't access the thing after destruction. Which includes making sure that there are no other references to the object being 'destroyed'. Think about what that would have to do to the garbage collection system (already a non-trivial piece of code). – Michael Kohne May 21 '14 at 21:52
  • Or, if it *doesn't* ensure that there are no other references, then imagine the memory problems that could occur with stray pointers... – Bobson May 21 '14 at 22:14
  • 2
    @Bobson no need to imagine, just look at your average C and C++ program... – jwenting May 22 '14 at 10:43

5 Answers5

7

We have to decouple two concepts here.

As of Java 7, the language does have RAII. It's called the try-with-resources statement.

static String readFirstLineFromFile(String path) throws IOException {
  try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  }
}

This guarantees that the reader will be closed no matter how the block exits.

Even without using try-with-resources, you can deterministically close a stream by calling br.close() when you want.

What's not deterministic is deleting the object from memory. That has to wait until there are no more references to the object and the garbage collector notices that. Trying to make that deterministic depends on potentially a lot of the program (e.g. does a listener, the debugger, or a logger still reference the object?) and the garbage collector's operational details (which GC algorithm is in use? is it a concurrent collector? which generation is the object in? how urgent is collection needed right now? ...).

gnat
  • 21,442
  • 29
  • 112
  • 288
Jerry101
  • 5,367
  • 1
  • 15
  • 19
  • 1
    This is a point I try to get across to people often. RAII happening at the point memory is reclaimed is an implementation detail. The important bit is the semantics, and those semantics can be achieved without the deterministic memory reclamation. – Phoshi May 22 '14 at 09:32
  • @Phoshi Yes! And the semantics are nicer since the object can still hold useful state after doing its cleanup, e.g. performance and debugging data, and its deallocation can be deferred while other code still references it. – Jerry101 May 23 '14 at 00:17
  • 5
    @Phoshi: True destructor-based RAII still has a huge advantage over try-with-resources/using in that it composes reliably. In C++ if you need an object to use several resources, you simply include appropriate subobjects and the compiler takes care of composing the destructors while with `IDisposable` you still have to write the `Dispose` method where it's still easy to miss something. – Jan Hudec May 23 '14 at 06:58
  • @JanHudec: In the IDisposable case, you have to write Dispose. In the dtor case, you have to write ~T. In the case your dtor has no important *semantics* (I.E., is all purely memory management) then the GC replaces that anyway. – Phoshi May 23 '14 at 08:38
  • 4
    @Phoshi: But not when I _compose_. Consider a class that manipulates three files for which it has members. In the IDisposable case the Dispose has to explicitly chain to Dispose of the members, in the destructor case it happens automatically. That is, the destructor _does_ have important semantics, but it gets is purely as _composition_ of the member destructors, so nothing needs to be written by hand (and don't forget all the nasty cases of exceptions thrown from constructor which proper use of RAII takes care of). – Jan Hudec May 23 '14 at 09:00
  • @JanHudec: Ah! Right, sorry, I misunderstood. This is true, you don't get that automatically in the disposable case, but you can write it in without increasing boilerplate at the callsite, which I think is the most important consideration. – Phoshi May 23 '14 at 09:14
  • @Phoshi: Composition is often also important, but Java designers apparently elected to keep the language simple in preference to adding another mechanism for it. [D](http://dlang.org/) and [Rust](http://www.rust-lang.org/) allow both value objects with destructors for full RAII and garbage collected objects, but they are more complicated. – Jan Hudec May 23 '14 at 09:24
  • @JanHudec: An expressive language should distinguish between references that encapsulate things that are "owned" by the owner of the reference, those that encapsulate unowned immutable objects, and references that encapsulate identity. Managing ownership is critical for types that encapsulate resources, but not really any less critical for types that encapsulate mutable state. If a language distinguishes distinguishes the different kinds of references, managing resources shouldn't be hard. A language can't manage resources, though, without knowing who owns them. – supercat Jul 09 '14 at 23:26
  • @supercat: So you say Java is not very expressive language. I agree. – Jan Hudec Jul 13 '14 at 10:17
  • @JanHudec: My point is that Java doesn't just lack deterministic destruction; it lacks a fundamental prerequesite (which would IMHO be useful with or without deterministic destruction). I wonder if there are any frameworks which can do dynamic binding via Reflection--as is possible in Java and .NET--but also have a well-defined "ownership" model. IMHO, such a model could have HUGE advantages, since it would allow auto-generation of `equals` and `clone` in most cases, while eliminating the need for defensive copies in many others. For example, an object encapsulating a collection... – supercat Jul 13 '14 at 16:29
  • ...could safely have a method that returned a reference to the original backing store, if the compiler would implicitly make a copy of the collection before allowing the recipient to modify it or persist a reference. If the recipient only wanted to use the reference in ways consistent with an ephemeral read-only reference, however, no copy would be needed. Not sure if or when the market will be ready for the next new Framework, though, or whether it will incorporate any such features, but I'd sure like to see them. – supercat Jul 13 '14 at 16:32
  • @supercat: I don't know; D and Rust both only allow compile-time reflection. But for modification, even C++'s `const` qualifier is enough (D and Rust both have it, extended to fully transitive). Of course the recipient has to create the copy manually, but that is usually desired as the programmer should be aware whether the original object is modified or not. – Jan Hudec Jul 13 '14 at 16:39
  • @JanHudec: A programmer wanting to modify an object would have to make a copy first, *unless the object it was given was already a detached copy*. Consider also the situation where a programmer wants an immutable snapshot of a collection and receives an object which might be immutable, or might be a read-only view of a mutable collection. I know that in general copy-on-write paradigms end up being expensive because of thread-safety issues, but I would think that there could be considerable benefits to a copy-on-write paradigm which didn't try to avoid absolutely all redundant copies... – supercat Jul 13 '14 at 16:54
  • ...but recognized the concepts of "thing which is either a sharable reference to an immutable object, or an exclusively-owned reference to a mutable object", "thing which is either a sharable immutable object, or which guaranteed to be *the only reference anywhere in the universe* to a mutable object", and "thing of unknown ownership and immutability". Under such as scheme, not all redundant copies could be eliminated, but all objects would be made permanently "mutable" or "immutable" before being stored to a field, thus eliminating any requirement for locking or atomic primitives. – supercat Jul 13 '14 at 17:12
  • @supercat: If you have `const` (so you can pass read-only views normally), the only redundant copies tend to be when you need to create a copy from something of which you have the last reference. Eliminating which may or may not be worth the added complexity. Most redundant copies exist in Java and C# as defensive coding because `const` references are not available. – Jan Hudec Jul 13 '14 at 18:28
  • @JanHudec: Defensive copying is necessary not only when exposing the state of a privately-held mutable object, but also when trying to capture the state of a read-only view into an immutable object. "Const" references will help with the former, but not the latter. Making a redundant copy of something which has been mutated but won't be anymore is generally fine, but making a redundant copy of something that never has and never will be mutated, when that redundant copy will probably be redundantly copied many more times, isn't so fine. – supercat Jul 14 '14 at 05:11
5

I have often pondered the same question.

I was a C++ programmer for many years and an Objective C programmer for many years before that. That experience taught me the discipline to track every object creation and achieve the nirvana of code - to logically assure the ultimate destruction of every created object and buffer.

And what a discipline it was - I could guarantee that I never leaked memory anywhere in my code. But memory leaked! Any library I used was a chink in this armor. Threading was a nightmare!! The discipline was valuable but it just did not solve the problem. Even the most carefully written code would require a restart after a few weeks of continuous running and many apps could not run stably for more than about 24 hours.

The solution is Garbage Collection. It makes everything so easy - you just let go of the memory and it floats off like a Helium-filled balloon to be gathered and recycled.

There are two primary benefits that make it a winner:

  1. It is simple - just let go of the object.
  2. The GC process also compacts your heap.

Now those two alone creates an order of magnitude improvement in the stability of your code and the ease of writing and debugging. The only downside is you have to suffer the indignity of occasionally putting up with the inevitable delay of the GC.

A quick consideration of allowing a mixture of immediate and postponed discard also proves pointless.

Say you have a Map tha you have filled with some data and now you know without a doubt that it will not be needed again. Surely manually freeing it would save you peanuts - just the nodes, not the keys or the values. A tiny gain of some fragmented values.

Bottom line - it's so simple even an incompetent programmer can write leak-free code.

OldCurmudgeon
  • 778
  • 5
  • 11
  • 4
    Well, technically you can still leak. If your code is poorly organized and you unintentionally keep references around, you have what's still functionally a memory leak. – N Jones May 22 '14 at 02:40
  • @NJones - Indeed you can but you have to do it in a specific way to make it happen. Just leaving things attached to other things isn't enough - you have to leave them attached to a running thread or class loader. Just letting go is almost always enough. – OldCurmudgeon May 22 '14 at 07:44
  • How exactly does one go about recycling helium from a balloon floating off? – Cerad May 22 '14 at 19:12
  • @Cerad - When the balloon bursts in the upper atmosphere the Helium is returned to nature. How arrogant of you to suggest that mankind should retain control - surely that would be the leak! :D – OldCurmudgeon May 22 '14 at 22:59
  • @OldCurmudgeon when the balloon bursts in the upper atmosphere the helium continues drifting off into space. Helium is never recovered and one day we will run out. – gbjbaanb Jun 26 '14 at 07:29
  • @gbjbaanb: The reason helium exists on this planet is that it is produced when radioactive elements undergo "alpha" decay. Such a decay causes the element to emit a helium nucleus at moderate velocity; the electrons don't go with the nucleus, but the primary product of the decay will be left with two extra electrons which will flow to whatever object nearby is most positively charged; that object can in turn pass a couple of electrons to someone else, etc. until eventually the helium nucleus ends up with a couple of electrons to go with it. – supercat Dec 29 '14 at 18:46
3

Going off of here, which lists the design goals of the Java programming language (emphasis mine):

1.2.2 Robust and Secure

The Java programming language is designed for creating highly reliable software. It provides extensive compile-time checking, followed by a second level of run-time checking. Language features guide programmers towards reliable programming habits. The memory management model is extremely simple: objects are created with a new operator. There are no explicit programmer-defined pointer data types, no pointer arithmetic, and automatic garbage collection. This simple memory management model eliminates entire classes of programming errors that bedevil C and C++ programmers. You can develop Java code with confidence that the system will find many errors quickly and that major problems won't lay dormant until after your production code has shipped.

The bolded portion, I think, answers your original question pretty well.

As for the second part of your question, I don't think I can personally answer that sufficiently. However, googling led me to this answer by Jon Skeet on Stack Overflow. He mentions that deterministic destructors tend to involve reference counting, which impacts performance and fails for cycles, and refers readers to this email by Brain Harry on resource management. I cannot verify myself whether these answers are true, but they both point towards deterministic destructors not being worth implementing, as their flaws outweighed the potential benefits of implementation.

(By the way, I think that the linked sources would provide too much information to be reasonably quoted here. If I should quote them anyways, let me know)

Glorfindel
  • 3,137
  • 6
  • 25
  • 33
awksp
  • 131
  • 8
3

The GC attempts to emulate a world where we never run out of memory, and that's where the advantages in terms of speed and maintainability come from.

In theory, the garbage collector should know more about whether now is a good time to destroy objects than you will while programming it. Often, programs in languages with less abstraction free memory as soon as possible. However, that's not always good for performance. So you might free memory later, where the place that the objects are allocated and destroyed is far removed. This increases the potential that the programmer will miss something. So it can be bad for memory usage, since things should be destroyed when the system gets some breathing room rather than in the middle of a high speed loop. But it can be faster that way.

If the garbage collector is able to run in a separate thread and do its work without corrupting the main thread's memory, then it will be faster than if the main thread handles its memory manually.

In practice, it depends on a lot of different things. Can the process be multithreaded, how much memory is available (less means more collections, more means faster speed), etc. While you can also force garbage collection, that's a very often misused feature. It may clean up those objects, but many times there are references the programmer didn't realize they were still using. So forcibly destroying those objects could create problems that wouldn't become apparent until much later, where the GC knows those objects are still in use.

Combined with recycling, so that the memory for objects doesn't need to be reallocated unless necessary, you can get much faster processing with a lot less work and a lot better maintainability.

jzx
  • 352
  • 2
  • 13
  • In what circumstances is it possible for the garbage collector to free objects which may be used later? AFAIK if there is still a reference to the object that the programmer can use at that point in the code the object will not be garbage collected. – Andris May 22 '14 at 04:38
  • The statement that GC attempts to emulate infinite memory is suspect. If you try to create an object and there's no memory left, allocation will fail; just like `malloc` or `new`. And it'll fail with an exception that can be caught and handled if you really want to - just like you can handle `malloc` or `new` failures. There's never been any pretense of infinite memory. – Doval May 22 '14 at 13:23
  • @Andris Yes, as I said the GC must know when an object has the potential of being called on again in order to prevent "programmer forgot X was still in use" errors. Object pooling is something that has to be programmed, using things like weak references and such. By indicating to the GC that objects may be reused, the GC can still dispose of them if it needs the space *or* they can be rehydrated and strongly referenced again without incurring an allocation cost. – jzx May 22 '14 at 16:15
  • @Doval Yes, if you run out of memory then you're out. But we shouldn't need to worry about how much memory we have, because the available memory is something that can be optimized at runtime according to system state. For example, in a variation of GC, large underutilized objects *could* be jogged to slower media depending on usage to make room for new objects, and then restored to faster media when necessary. It just so happens that (in normal GC) unused objects are moved to oblivion, and sometimes we recreate them exactly the same when needed. – jzx May 22 '14 at 16:26
  • @jzx Nothing stops you from having those kinds of things in manual memory management, since no rule says a pointer is a literal memory address (or even that pointers to objects and pointers to functions have the same representation). And memory obtained from malloc can still come from memory pages on disc. There's obvious differences in both systems regarding when memory is freed, but there's never been a pretense of infinite supply. – Doval May 22 '14 at 16:32
  • @Doval Yes, in practice there's no such thing as infinite memory. But the goal is to allow things to be compressed, moved, removed, or recycled without interrupting the programmer's intention for writing the application. When someone writes a program without garbage collection, they *also* need to write a memory manager that will not only be mixed in with the business logic of their code but is likely to be much less effective than one that can adapt to conditions at runtime. So instead we act as if there is unlimited memory and handle runout as an exceptional case. – jzx May 22 '14 at 16:50
  • We do not "act as if there's unlimited memory." We act as if unused pieces will be freed automatically; we act as if we will take up no more memory than our working set. No one in their right mind thinks that the objects they create don't take up space or that the garbage collector will continue to produce memory out of thin air. Not in theory and not in practice. – Doval May 22 '14 at 16:57
  • The point is that running out of memory should be an edge case, not something you have to worry about all the time. Unless you're working with extremely large amounts or sizes of data, memory requirements shouldn't even enter into your mind. – jzx May 22 '14 at 17:08
  • Nitpick: modern garbage collectors tend to be based on the idea of copying the live objects to a fresh space, so although we can still reason about objects' lifetimes, there is no real 'destruction', instead there's the idea of 'surviving'. The time complexity of the GC isn't affected by the number of objects that don't survive. – Max Barraclough May 09 '18 at 10:00
-1

Java makes auto cleanup:

As we all know Garbage collector do the clean up job automatically but WHEN? Every object is eligible for cleanup when it has no links.

Car c1=new Car();---Object 1
c1=new Car(); ---Object 2

In line 2, Object 1 is eligible for garbage collection but this would be collected or not, it depends on many other factors. There are three heap spaces Eden, Survivor, Tenured Generation and JVM always wants a proper balance of memory(a proper saw tooth graph). Every object born in Eden space and follow other heap spaces according to it's age in program. So when any of these heap spaces fulls GC searches for objects with no links and make the cleanup for those object and shifts the remaining object to next heap space.

That's why in JDBC if we don't close Connections, Resultsets, Statements then there is a condition of OutOfMemory.

So JVM follow this type of cleanup management which it successful in most of the cases and there is no need of customized memory management. Even for preventing the cases like JDBC outofmemory problem java introduces try with resources, which frees the object at the end of try's scope.

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  }

Don't confuse with finalize() method it just calls before garbage collection invocation, it has no connection to customized memory clean like destructors in C++.

JavaDT
  • 99
  • 1
  • and don't forget that finalize() is never guaranteed to be called... – jwenting May 23 '14 at 06:29
  • Yes definitely. :) – JavaDT May 23 '14 at 06:30
  • this seems to merely repeat points made and explained yesterday, in [this answer](http://programmers.stackexchange.com/a/240789/31260) – gnat May 23 '14 at 06:58
  • @gnat If you read both answers then you will get the difference. In latter one there is no concept of Heap Mechanism for memory cleanup. I just gave the try with resource example to overcome the problem of OutOfMemory. Still you think this answer should be downvoted then it's up to you. Thanks :) – JavaDT May 23 '14 at 07:02
  • as far as I can tell, "concept of heap mechanism" has also been explained in good details, in other two answers posted yesterday: [here](http://programmers.stackexchange.com/a/240771/31260) and [here](http://programmers.stackexchange.com/a/240769/31260) – gnat May 23 '14 at 07:05
  • If you think so then it's Ok. But I think this answer should be here. :) – JavaDT May 23 '14 at 07:07