OutOfMemoryError 到底能不能被捕获?
感觉中,OutOfMemeryError(内存溢出错误) 是jvm抛出的异常,是不能被捕获的。
直到工作中真的遇到OOM异常,而且tomcat服务还一直对外提供服务。
那么问题来了:
1. OOM 到底能不能被捕获?
2. jvm抛出OOM后,是否就会立即停止运行呢?
3. jvm什么时候会抛出OOM异常?
先来个例子:
本例子将会一一体现如上问题:(最好设置jvm最大内存如: -Xmx2m -Xms2m)
public class OOMCatchTest { /** * 可以看作是一个消息队列, 作为 Producer 与 Consumer 的通信桥梁 <br /> * 其实此处存在并发写的问题,不过不是本文的重点,暂且忽略 */ private static volatile List<UserObj> userWaitingList = new ArrayList<>(); private AtomicInteger uidCenter = new AtomicInteger(0); // 随机数生成源 private final String rndSource = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; public static void main(String[] args) throws IOException { OOMCatchTest oomCatchTest = new OOMCatchTest(); Thread producer = new Thread(new Runnable() { @Override public void run() { System.out.println(System.currentTimeMillis() + ": start producer."); oomCatchTest.productUserObj(); System.out.println(System.currentTimeMillis() + ": end producer."); } }); producer.setName("producer-1"); producer.start(); Thread consumer = new Thread(() -> { System.out.println(System.currentTimeMillis() + ": start consumer."); try { oomCatchTest.consumeUserObj(); } catch (Throwable e) { System.out.println("consumer caught exception: " + e.getMessage()); e.printStackTrace(); } System.out.println(System.currentTimeMillis() + ": end consumer."); }); consumer.setName("consumer-1"); consumer.start(); System.out.println("over the main"); } // 生产者 public void productUserObj() { Random rnd = new Random(); OOMCatchTest oomTest = new OOMCatchTest(); // 可作开关 boolean startProduce = true; try { while (startProduce) { UserObj userTemp = new UserObj(); userTemp.setAddress(oomTest.generateRandomStr(20)); userTemp.setAge(rnd.nextInt(100)); userTemp.setUid(oomTest.generateUid()); userTemp.setName(oomTest.generateRandomStr(10)); // 此处展示 ArrayList 导致的抛出OOM类型 userWaitingList.add(userTemp); System.out.println("produce:" + userTemp); } } // 此处可捕获 OOM catch (Throwable e) { // 模拟一个服务提供者,做死循环 System.out.println("An Exception: " + e.getClass().getCanonicalName() + " " + e.getMessage() + " occour..., cur uid:" + oomTest.uidCenter); int j = 0; // 此处运行代表 OOM 并未终止jvm while (j++ < 1000) { try { Thread.sleep(1000); System.out.println("producer oom, wait: " + j); } catch (InterruptedException e1) { e1.printStackTrace(); } } // 如果打印栈桢,只会更增加内存消耗,从而导致线程异常退出 // e.printStackTrace(); } } // 消费者 public void consumeUserObj() { // 可做开关 boolean startConsume = true; while(startConsume) { // 做空等等 while (userWaitingList.isEmpty()) { Thread.yield(); } while (userWaitingList.iterator().hasNext()) { try { // do sth Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } UserObj obj = userWaitingList.iterator().next(); System.out.println("consume user:" + obj); userWaitingList.remove(obj); } } } public String generateRandomStr(int digit) { StringBuilder sb = new StringBuilder(); int len = rndSource.length(); Random random = new Random(); for(int i = 0; i < digit; i++) { sb.append(rndSource.charAt(random.nextInt(len))); } return sb.toString(); } public Integer generateUid() { return uidCenter.incrementAndGet(); } static class UserObj { private Integer uid; private String name; private Integer age; private String address; public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "UserObj{" + "uid=" + uid + ", name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}'; } } }
如上例子,可能抛出如下异常:
produce:UserObj{uid=2135, name='7QSy8X251t', age=44, address='2wwye8WEfR6dHJEQrIHk'} An Exception: GC overhead limit exceeded occour..., cur uid:2136 java.lang.OutOfMemoryError: GC overhead limit exceeded consume user:UserObj{uid=1, name='nBf1Ck3T2G', age=20, address='7ubqHrfiHf5WEdPtbJak'} at java.util.Arrays.copyOfRange(Arrays.java:3664) at java.lang.String.<init>(String.java:207) at java.lang.StringBuilder.toString(StringBuilder.java:407) at com.xxx.tester.OOMCatchTest.generateRandomStr(OOMCatchTest.java:98) at com.xxx.tester.OOMCatchTest.productUserObj(OOMCatchTest.java:58) 1541324629155: end producer. at com.xxx.tester.OOMCatchTest$1.run(OOMCatchTest.java:26) at java.lang.Thread.run(Thread.java:745) Exception in thread "consumer-1" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.util.Arrays.copyOfRange(Arrays.java:3664) at java.lang.String.<init>(String.java:207) at java.lang.StringBuilder.toString(StringBuilder.java:407) at com.xxx.tester.OOMCatchTest$UserObj.toString(OOMCatchTest.java:145) at java.lang.String.valueOf(String.java:2994) at java.lang.StringBuilder.append(StringBuilder.java:131) Exception in thread "consumer-1" java.lang.OutOfMemoryError: GC overhead limit exceeded java.lang.OutOfMemoryError: GC overhead limit exceeded An Exception: GC overhead limit exceeded occour..., cur uid:11743 at java.util.Arrays.copyOf(Arrays.java:3332) Exception in thread "producer-1" java.lang.OutOfMemoryError: GC overhead limit exceeded Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "consumer-1" Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "producer-1" produce:UserObj{uid=3751, name='dtXmpNGMs1', age=41, address='1Bujt5TzHv04cptNEyUb'} An Exception: java.lang.OutOfMemoryError GC overhead limit exceeded occour..., cur uid:3752 producer oom, wait: 1 producer oom, wait: 2 producer oom, wait: 3
抛出OOM异常有几种情况:
1. java中直接throw 抛出 OOM;(后面详细列举)
2. 使用new int[MAX] 等基本类型方式时抛出 OOM,这种异常隐式抛出;
3. 当收到外部特殊信号时抛出,如:常用的威胁信号 kill -3 <pid>;
而通常,前两个OOM都是可能被捕获的! 且抛出的OOM只会影响当前线程(和其他异常一样)。不过 OOM 一般会具有普遍性,即一个线程OOM时,通常其他线程也跑不掉!
下面来看几个JAVA中主动抛出 OOM 的样例吧:
// java.util.ArrayList.add(E e), 进行扩容的时候,就可能抛出oom, 也即代码异常,可以捕获
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } // java.nio.ByteBuffer.allocateDirect(int capacity); //分配直接内存时,可能抛出oom /** * Allocates a new direct byte buffer. * * <p> The new buffer's position will be zero, its limit will be its * capacity, its mark will be undefined, and each of its elements will be * initialized to zero. Whether or not it has a * {@link #hasArray backing array} is unspecified. * * @param capacity * The new buffer's capacity, in bytes * * @return The new byte buffer * * @throws IllegalArgumentException * If the <tt>capacity</tt> is a negative integer */ public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); } // java.nio.DirectByteBuffer DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); // 此处先抛出oom Bits.reserveMemory(size, cap); long base = 0; try { base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; } // which a process may access. All sizes are specified in bytes. static void reserveMemory(long size, int cap) { if (!memoryLimitSet && VM.isBooted()) { maxMemory = VM.maxDirectMemory(); memoryLimitSet = true; } // optimist! if (tryReserveMemory(size, cap)) { return; } final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess(); // retry while helping enqueue pending Reference objects // which includes executing pending Cleaner(s) which includes // Cleaner(s) that free direct buffer memory while (jlra.tryHandlePendingReference()) { if (tryReserveMemory(size, cap)) { return; } } // trigger VM's Reference processing System.gc(); // a retry loop with exponential back-off delays // (this gives VM some time to do it's job) boolean interrupted = false; try { long sleepTime = 1; int sleeps = 0; while (true) { if (tryReserveMemory(size, cap)) { return; } if (sleeps >= MAX_SLEEPS) { break; } if (!jlra.tryHandlePendingReference()) { try { Thread.sleep(sleepTime); sleepTime <<= 1; sleeps++; } catch (InterruptedException e) { interrupted = true; } } } // no luck throw new OutOfMemoryError("Direct buffer memory"); } finally { if (interrupted) { // don't swallow interrupts Thread.currentThread().interrupt(); } } } // java.util.concurrentHashMap.toArray(); // public final Object[] toArray() { long sz = map.mappingCount(); if (sz > MAX_ARRAY_SIZE) // Required array size too large throw new OutOfMemoryError(oomeMsg); int n = (int)sz; Object[] r = new Object[n]; int i = 0; for (E e : this) { if (i == n) { if (n >= MAX_ARRAY_SIZE) throw new OutOfMemoryError(oomeMsg); if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) n = MAX_ARRAY_SIZE; else n += (n >>> 1) + 1; r = Arrays.copyOf(r, n); } r[i++] = e; } return (i == n) ? r : Arrays.copyOf(r, i); }
// java.nio.file.Files.readAllBytes(Path path)
public static byte[] readAllBytes(Path path) throws IOException { try (SeekableByteChannel sbc = Files.newByteChannel(path); InputStream in = Channels.newInputStream(sbc)) { long size = sbc.size(); if (size > (long)MAX_BUFFER_SIZE) throw new OutOfMemoryError("Required array size too large"); return read(in, (int)size); } } /** * Reads all the bytes from an input stream. Uses {@code initialSize} as a hint * about how many bytes the stream will have. * * @param source * the input stream to read from * @param initialSize * the initial size of the byte array to allocate * * @return a byte array containing the bytes read from the file * * @throws IOException * if an I/O error occurs reading from the stream * @throws OutOfMemoryError * if an array of the required size cannot be allocated */ private static byte[] read(InputStream source, int initialSize) throws IOException { int capacity = initialSize; byte[] buf = new byte[capacity]; int nread = 0; int n; for (;;) { // read to EOF which may read more or less than initialSize (eg: file // is truncated while we are reading) while ((n = source.read(buf, nread, capacity - nread)) > 0) nread += n; // if last call to source.read() returned -1, we are done // otherwise, try to read one more byte; if that failed we're done too if (n < 0 || (n = source.read()) < 0) break; // one more byte was read; need to allocate a larger buffer if (capacity <= MAX_BUFFER_SIZE - capacity) { capacity = Math.max(capacity << 1, BUFFER_SIZE); } else { if (capacity == MAX_BUFFER_SIZE) throw new OutOfMemoryError("Required array size too large"); capacity = MAX_BUFFER_SIZE; } buf = Arrays.copyOf(buf, capacity); buf[nread++] = (byte)n; } return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); } // java.io.BufferedInputStream.read()/skip()/ // java.io.BufferedInputStream.fill() private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; /* no mark: throw away the buffer */ else if (pos >= buffer.length) /* no room left in buffer */ if (markpos > 0) { /* can throw away early part of the buffer */ int sz = pos - markpos; System.arraycopy(buffer, markpos, buffer, 0, sz); pos = sz; markpos = 0; } else if (buffer.length >= marklimit) { markpos = -1; /* buffer got too big, invalidate mark */ pos = 0; /* drop buffer contents */ } else if (buffer.length >= MAX_BUFFER_SIZE) { throw new OutOfMemoryError("Required array size too large"); } else { /* grow buffer */ int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? pos * 2 : MAX_BUFFER_SIZE; if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { // Can't replace buf if there was an async close. // Note: This would need to be changed if fill() // is ever made accessible to multiple threads. // But for now, the only way CAS can fail is via close. // assert buf == null; throw new IOException("Stream closed"); } buffer = nbuf; } count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; }
// java.lang.AbstractStringBuilder.expandCapacity(int minimumCapacity)
// java.lang.AbstractStringBuilder.ensureCapacityInternal(int minimumCapacity)
// java.lang.AbstractStringBuilder.append(String str)
/** * This implements the expansion semantics of ensureCapacity with no * size check or synchronization. */ void expandCapacity(int minimumCapacity) { int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } value = Arrays.copyOf(value, newCapacity); }
扩展,实际使用中捕获OOM的案例:
dubbo 捕获的 server 端的 OOM 异常示例如下:
2018-11-01 17:23:55.814 [DubboServerHandler-172.23.0.28:20880-thread-198] ERROR com.alibaba.dubbo.rpc.filter.ExceptionFilter - [DUBBO] Got unchecked and undeclared exception which called by 172.25.0.12. service: com.mobanker.xsxf.sxk.contract.basedata.RpcProtocolService, method: getUserProtocol, exception: java.lang.OutOfMemoryError: Java heap space, dubbo version: 3.0.0, current host: 172.23.0.28 java.lang.OutOfMemoryError: Java heap space at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.instantiate(JavaDeserializer.java:271) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:155) at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:397) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2070) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2005) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1990) at com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectInput.readObject(Hessian2ObjectInput.java:88) at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:94) at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcResult.decode(DecodeableRpcResult.java:117) at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody(DubboCodec.java:98) at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:134) at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:95) at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.decode(DubboCountCodec.java:46) at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.messageReceived(NettyCodecAdapter.java:134) at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:80) at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564) at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:559) at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:274) at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:261) at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:351) at org.jboss.netty.channel.socket.nio.NioWorker.processSelectedKeys(NioWorker.java:282) at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:202) at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108) at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:44) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
捕获代码:
@Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { try { Result result = invoker.invoke(invocation); if (result.hasException() && GenericService.class != invoker.getInterface()) { try { Throwable exception = result.getException(); // directly throw if it's checked exception if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // directly throw if the exception appears in the signature try { Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class<?>[] exceptionClassses = method.getExceptionTypes(); for (Class<?> exceptionClass : exceptionClassses) { if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException e) { return result; } // for the exception not found in method's signature, print ERROR message in server's log. logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception); // directly throw if exception class and interface class are in the same jar file. String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) { return result; } // directly throw if it's JDK exception String className = exception.getClass().getName(); if (className.startsWith("java.") || className.startsWith("javax.")) { return result; } // directly throw if it's dubbo exception if (exception instanceof RpcException) { return result; } // otherwise, wrap with RuntimeException and throw back to the client return new RpcResult(new RuntimeException(StringUtils.toString(exception))); } catch (Throwable e) { logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); return result; } } return result; } catch (RuntimeException e) { logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); throw e; } }
tomcat 中捕获OOM的异常示例如下:
28-Oct-2018 14:21:04.258 严重 [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run Unexpected death of background thread ContainerBackgroundProcessor[StandardEngine[Catalina]] java.lang.OutOfMemoryError: Java heap space Exception in thread "ContainerBackgroundProcessor[StandardEngine[Catalina]]" java.lang.OutOfMemoryError: Java heap space
捕获代码:
// -------------------------------------- ContainerExecuteDelay Inner Class /** * Private thread class to invoke the backgroundProcess method * of this container and its children after a fixed delay. */ protected class ContainerBackgroundProcessor implements Runnable { @Override public void run() { Throwable t = null; String unexpectedDeathMessage = sm.getString( "containerBase.backgroundProcess.unexpectedThreadDeath", Thread.currentThread().getName()); try { while (!threadDone) { try { Thread.sleep(backgroundProcessorDelay * 1000L); } catch (InterruptedException e) { // Ignore } if (!threadDone) { processChildren(ContainerBase.this); } } } catch (RuntimeException|Error e) { t = e; throw e; } finally { if (!threadDone) { log.error(unexpectedDeathMessage, t); } } } protected void processChildren(Container container) { ClassLoader originalClassLoader = null; try { if (container instanceof Context) { Loader loader = ((Context) container).getLoader(); // Loader will be null for FailedContext instances if (loader == null) { return; } // Ensure background processing for Contexts and Wrappers // is performed under the web app's class loader originalClassLoader = ((Context) container).bind(false, null); } container.backgroundProcess(); Container[] children = container.findChildren(); for (int i = 0; i < children.length; i++) { if (children[i].getBackgroundProcessorDelay() <= 0) { processChildren(children[i]); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error("Exception invoking periodic operation: ", t); } finally { if (container instanceof Context) { ((Context) container).unbind(false, originalClassLoader); } } } }
总结:
1. oom异常一般是java程序在做内存分配时,发现没有足够的剩余空间可用而抛出的异常;
2. 此时的分配空间可能是出于代码的new操作(用户主动),可能是出于内存的复制操作(语言自动),也可能是出于内存数据的重振操作(语言自动),可能是出jvm检测到外部信号(jvm自动);
3. oom只是被建议为不要捕获的异常,因为通常你对这种情况是无能为力的!但你如果实在要捕获,why not ?
4. oom一般只会影响当前线程,而jvm中只要存在一个非daemon线程在运行,jvm就不会退出;
5. 如果是线程池运行环境,一般需要一个统一管理oom的程序,否则不能及时统一处理oom;