3

The general advice is that you should not catch java.lang.Error except in special circumstances, see Is it a bad practice to catch Throwable? for instance.

My situation is that I have a program which sometimes runs out of memory and throws java.lang.OutOfMemoryError. Although there's no recovery from this I do want to know it happened, so I wish to see something in the log and a non-zero exit code. So is something like this adviseable?

public static void main(String[] args)
{
    try
    {
        ...
    }
    catch (Exception e)
    {
        e.printStackTrace();
        System.exit(1);
    }
    catch (OutOfMemoryError e)
    {
        e.printStackTrace();
        System.exit(1);
    }
}

Another program is similar except that it may be one particular thread that is consuming all the memory. In this case if that thread exits it is possible to continue processing, again all I really want is to see a log and to ultimately have a non-zero exit code. So should I catch the OutOfMemoryError in that threads run method?

Community
  • 1
  • 1
john
  • 7,667
  • 24
  • 26
  • What, exactly, would you do with it if you caught it? If you run out of memory, then what do you think you'll be able to log? What's the point? –  Jan 17 '13 at 10:28
  • In your code example, printStackTrace creates a few objects and might throw another OOME and therefore not complete, but apart from that it can't really harm more than what you have. – assylias Jan 17 '13 at 10:28
  • catching the exception and doing something else can even cause other OutOfMemoryError to be thrown! – Narendra Pathai Jan 17 '13 at 10:29
  • The point is to know that it happened, so that production jobs can be rerun with more memory if necessary, or with the large records that caused the problem removed. – john Jan 17 '13 at 10:29
  • Again: if you have no available memory, then how can you log anything? –  Jan 17 '13 at 10:30
  • I've tested this code, and it does create a log in this situation. Even if no log is possible a non-zero error code is essential. – john Jan 17 '13 at 10:31
  • I do not see it bad if you have a particular piece of code where the OOME is a distinct possibility. Exiting that piece of code will mean that probably a lot of objects will get dereferenced, so there is a possibility for recovery (note that not certainty). And my first line of code in the catch would be `System.gc();`. – SJuan76 Jan 17 '13 at 10:32
  • @JackManey OOME could be thrown because a large object could not be created - there might be some memory left (at least enough to log and exit). – assylias Jan 17 '13 at 10:32
  • 1
    @SJuan76 The explicit `System.gc()` is entirely redundant. No sane GC will throw an OOME if there is garbage to be disposed of. – Marko Topolnik Jan 17 '13 at 10:33
  • @SJuan76 GC is guaranteed to run before an OOME is thrown. – assylias Jan 17 '13 at 10:33
  • @assylias Note that it is not guaranteed by specification; only by the "sanity clause" :) – Marko Topolnik Jan 17 '13 at 10:34
  • @MarkoTopolnik [here](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.3): *OutOfMemoryError: The Java virtual machine implementation has run out of either virtual or physical memory, and the automatic storage manager was unable to reclaim enough memory to satisfy an object creation request.* Also [in this post](http://stackoverflow.com/questions/12298725/is-the-garbage-collector-guaranteed-to-run-before-out-of-memory-error). – assylias Jan 17 '13 at 10:36
  • @assylias Yes, it's quite vague, isn't it? A no-op automatic storage manager is definitely "unable to reclaim enough memory" :) – Marko Topolnik Jan 17 '13 at 10:37
  • @MarkoTopolnik Yes ok we assume that there is a proper GC on the JVM otherwise, well... – assylias Jan 17 '13 at 10:37
  • @MarcoTopolnik I do not understand your answer; if `assylias` was right then it would be redundant (but you don't agree with him). I am not expecting the `System.gc();` to cause OOME, but most likely you will have `System.gc();` + some handling code. Since `System.gc()` guarantees nothing, you cannot trust that the handling code after it will not still cause OOME. – SJuan76 Jan 17 '13 at 10:39
  • @SJuan76 assylias and I are basically leading an armchair discussion. There is no **hard guarantee** regardless of whether you do or don't call `System.gc()`, but in practice you'll never get an OOME if the GC still had a chance to reclaim something. The only exception is a case where the runtime detects that the whole JVM is doing nothing else but GC-ing, leaving no CPU to the actual work (this happens when there is always just a bit of memory to reclaim in a major GC, just enough to keep going). – Marko Topolnik Jan 17 '13 at 10:43

4 Answers4

7

There is perfect sense in having an exception barrier at the very top of your call stack, catching and logging all Throwables. In server-side code this is in fact the norm. If you make sure to catch the OutOfMemoryError only at that level, and not anywhere lower, there is a very large chance that it will not harm your system: as the call stack unwinds, all the objects created to serve the request will become unreachable. Since it is very likely that the OOME occurred precisely in the thread which was inflicting the strongest memory pressure on the system, all that memory will be reclaimed and the rest of the system will be able to breathe again.

Yes, technically there's always a chance to get an OOME inside a finally block, causing a resource leak or worse; or inside some code which was modifying a long-lived, global structure, breaking its invariants, but it is quite unlikely in practice.

When deciding on your policy for OOMEs keep in mind that your application is subject to many unpredictable factors which are more or less likely to deteriorate its stability. OOME is just another point in that spectrum, and typically its risk impact is not particularly high.

Marko Topolnik
  • 179,046
  • 25
  • 276
  • 399
  • +1 - Catch all `Trowable`s (not just `Error`s) and log them. Very important. – OldCurmudgeon Jan 17 '13 at 10:37
  • The OP:s code could not possibly cause an infinite loop of OOME could it? If an OOME is raised on e..printStackTrace(), it is thrown in the catch block, not in the try block. So there will be nothing to catch the second OOME. Or am I understanding something wrong? – Alderath Jan 17 '13 at 10:48
  • @Alderath I didn't mean that kind of loop, but trying to repeat the work that OOME'd, just to end up with another OOME, or trying to serve any further requests, which all OOME because all that memory was retained in static/instance fields. – Marko Topolnik Jan 17 '13 at 10:50
  • My bad. Maybe I took your statement out of context a bit. The infiinite loop problem can occur for any kind of retry-strategy for any exception though. You just have to keep set some flag/counter when you attempt to retry, so that you attempt to retry an infinite number of times. – Alderath Jan 17 '13 at 11:21
  • I like the point about stack unwinding. That will free up all the memory usage that caused the problem in the first place, and mean that logging should work fine. When I said there's no recovery, I really meant for this program no point in trying to redo the work that caused the problem in the first place. – john Jan 17 '13 at 14:22
  • @john By "recovery" it is usually meant "returning to a stable application state". From that point one has the freedom of choice to retry---or not :) – Marko Topolnik Jan 17 '13 at 14:26
  • An unfortunate problem with catching `OutOfMemoryError` is that there is generally no way of knowing what other exceptions or errors may have been thrown but not yet handled, or whether any `finally`-block cleanup may have been forcibly abandoned. An `OutOfMemoryError` that occurs in a `Finally` block can wreak major havoc even stack unwinding frees up plenty of RAM. – supercat Oct 08 '14 at 20:53
  • @supercat Yes and not *just* that: it may occur in a thread which isn't even guilty of overallocation. That thread may be a crucial support thread. Further, OOME (just like SOE) may happen in the middle of an API call, destroying the API's invariants. But, on the bright side, practice shows that OOME's generally *are* recoverable and usually it doesn't pay to go into downtime due to an OOME (mission-critical apps aside). – Marko Topolnik Oct 09 '14 at 07:59
  • @MarkoTopolnik: I wish the JVM had means by which code could indicate (probably with a per-thread property) that it was prepared to have certain allocations fail, and such allocations fail with `LowMemoryTrapException` if having them succeed might leave insufficient memory for things that weren't supposed to fail; likewise with `LowStackTrapException`. – supercat Oct 09 '14 at 15:54
  • @MarkoTopolnik: It's not possible to perfectly predict the memory threshold where an application should start cleanly refusing operations which would require more memory, but I would expect that even setting a threshold at 80% utilization would probably work pretty well. It would reduce by at most 20% the amount of memory an application could usefully employ, while ensuring that an application whose larger memory consumers could refrain from allocating past 80% would be unlikely to crash from an unexpected OOME. – supercat Oct 09 '14 at 15:59
  • @MarkoTopolnik: Such a facility would be extremely helpful when writing code to read into memory as much data as a file contains. If files might legitimately be very large enough to occupy 99% of RAM, it would be better for a method to throw an exception saying, effectively, "this file is too big for the available RAM" than to have it succeed but leave almost no RAM available for everything else, especially if (as would often be the case) the application would be prepared for the possibility that the attempt to load the file might fail for other reasons. – supercat Oct 09 '14 at 16:06
2

It is common to catch it, but only at the highest level in your thread. The easiest way is to use an uncaughtexception handler. This is a function that is called when an exception is thrown. At that point you can log it and tell the user why you are exiting the application.

Thirler
  • 18,868
  • 13
  • 58
  • 86
0

The general rule is: any exception should be caught by the module which is most competent to react adequately. If current method does not know what to do, it should let the exception pass through, until reaching main() or run() methods. Those methods cannot hope there are more competent methods, so they can catch and log or watever.

Alexei Kaigorodov
  • 12,459
  • 1
  • 18
  • 36
0

In the example above, I think it is a good idea so you can control how your program shutdown. If you don't catch this error, other threads can continue to run incorrectly (The error is only thrown in one thread) It also give a exit code when a calling shell can check. I would use a different exit code for this error.

In general OOME is not guaranteed to be recoverable but doesn't guarantee your program will shut down either.

Peter Lawrey
  • 498,481
  • 72
  • 700
  • 1,075