OutOfMemoryError 到底能不能被捕获?

Java基础

浏览数:405

2019-6-8

  感觉中,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;

 

作者:等你归去来