📜 ⬆️ ⬇️

OutOfMemoryError: catch if you can

image


Hello! Today I would like to share the experience of the OOM error. This article prompted me to write a problem I encountered. And which, as it turned out later, remained unnoticed for a long time. I was interested in this question, so I decided to study it a little deeper.


Prehistory


We have a service that, according to the schedule, throws the data processing task in the ExecutorService . This is quite a difficult task. And at one point, the information just became more and it did not fit into our -Xmx .


OOM do it yourself


For testing, I need to fill in all the memory with objects that the GC will consider alive. For this, I used the following code:


public class MemoryGrabber { static final List<Object[]> arrays = new LinkedList<>(); public static void grabAllMemory() { for (; ; ) { arrays.add(new Object[100]); } } } 

There is also some problem here, but more on that later.


Plain code


 public class BadExecutor { private static final Logger logger = LogManager.getLogger(BadThread.class); private static final ExecutorService executor = Executors.newFixedThreadPool(5); public static void main(String[] args) throws Exception { executor.submit(() -> { try { grabAllMemory(); } catch (Exception e) { logger.error(e.getMessage()); } }); } } 

This code seems to be looking good, there is nothing special here. Probably many wrote something like this more than once. But the problem is that the PROM OOM will not display anything at all. Neither the log nor the output stream.


Catch Throwable - they said


Yes, sure, because OutOfMemoryError is an Error, not an Exception. Therefore, here he successfully slips past the catch block and is intercepted already in the code ThreadPoolExecutor . Where is swallowed, and the stream itself begins to wait for a new task. Everyone knows that at the root of the code it is better to catch Throwable .


Unfortunately, if instead of Exception in this situation to catch Throwable , nothing will change. When you call logger.error () , we just get a new OOM, which also sinks into the depths of the ThreadPoolExecutor .


It is worth noting that if, instead of ExecutorService , a new Thread were created, all errors would ultimately be handled by UncaughtExceptionHandler in case of a thread death, and there would be information in stderr. ThreadPoolExecutor tries to reuse threads, which is expected in principle.


Lost OutOfMemoryError


Throwing a task into ExecutorService , we forgot a very important thing - to use Future , which returns the submit () method.


 public class GetFuture { private static final Logger logger = LogManager.getLogger(BadThread.class); private static final ExecutorService executor = Executors.newFixedThreadPool(5); public static void main(String[] args) throws Exception { try { executor.submit(MemoryGrabber::grabAllMemory).get(); } catch (Throwable e) { logger.error(e); } } } 

Now it's a little better. If logger.error () throws out a new OOM, then the main thread will crash and possibly generate an error. This helps to pull the result from the ExecutorService out. Everyone saw something like this:


 Exception in thread "main" Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main" 

This message displays the default error handler that is called in case of an unexpected death of the thread.


UncaughtExceptionHandler - not a panacea


Do not rejoice before time, because it became better quite a bit. If you do not override the handler, then ThreadGroup.uncaughtException () is called, which contains the following code:


 System.err.print("Exception in thread \"" + t.getName() + "\" "); e.printStackTrace(System.err); 

The first line creates a new object using concatenation and, if a new OOM does not take off, then there is a high probability of getting it in printStackTrace () . It all depends on the circumstances. But the point is that even having received OOM in the main stream, there is a chance not to learn anything about it.


Finalize it


So, now our problem is that there is no memory for logging. Because of what we get the second error. So maybe try to free up space? The problem is that MemoryGrabber.array is a static variable. And the objects available through her GC considers alive. I'll try to clean it.


 public class FinalizeIt { private static final Logger logger = LogManager.getLogger(BadThread.class); private static final ExecutorService executor = Executors.newFixedThreadPool(5); public static void main(String[] args) throws Exception { try { executor.submit(() -> { try { grabAllMemory(); } finally { MemoryGrabber.arrays.clear(); //   } }).get(); } catch (Throwable e) { logger.error(e); } executor.shutdownNow(); } } 

Now during logging the garbage collector will be invoked, which can already remove the unnecessary data structure.


Ode to functional programming


At first, I said that there is a problem in MemoryGrabber . It is in the static variable array . The fact is that this variable continues to live after the moment everything fell with an error. The peep crutch is its zeroing in the finaly block. It would be much better if it was stored on the call stack.


 public class FunctionalGrabber { public static void grabAllMemory() { List<Object[]> arrays = new LinkedList<>(); for (; ; ) { arrays.add(new Object[10]); } } } 

Now our List sheet will turn into garbage as soon as the grabAllMemory method is completed . It does not matter, with or without error. Almost Scala.


How to do


I hope I managed to convey the idea that trying to catch and process OutOfMemoryError in the code is a dubious undertaking for a number of reasons. For these purposes, it is better to rely on the following JVM options:



The last two parameters appeared only in JDK 8u92, the others still in 1.4. The best behavior is to terminate the process in the case of an OutOfMemoryError. Such logic is the most understandable for all developers and those who will support the application. Attempts to handle such errors can lead to consequences that are not obvious even to the author himself.


findings


In the article I tried to understand some errors, which may cause problems when an OOM appears. To avoid them, you need to keep in mind:



')

Source: https://habr.com/ru/post/359300/


All Articles