文章且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版为您奉上珍贵的学习资源 :
免费赠送 :《尼恩Java面试宝典》持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备免费赠送 :《尼恩技术圣经+高并发系列PDF》,帮你 实现技术自由,完成职业升级, 薪酬猛涨!加尼恩免费领免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》面试必备 + 大厂必备 +涨薪必备 加尼恩免费领免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》面试必备 + 大厂必备 +涨薪必备 加尼恩免费领免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
(资料图)
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如美团、拼多多、极兔、有赞、希音的面试资格,遇到一几个很重要的面试题:
生产环境,很多小伙伴,也遇到过OOM后,JVM会退出的问题。
这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”。
也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典》V81版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
最新《尼恩 架构笔记》《尼恩高并发三部曲 》《尼恩Java面试宝典》 的PDF文件,请通过公号【技术自由圈】获取
问题是,咱们常常说:发生OOM,程序就会挂。
很多情况是: 发生OOM了,JVM没有挂。
来看一下 OutOfMemoryError,说到底,OutOfMemoryError 也只是一个java中的异常而已,
OutOfMemoryError 属于Error一系非检查异常, 其继承关系如下
Object Throwable Error VirtualMachineError OutOfMemoryError
再来看看,堆内存不够与 OutOfMemoryError 异常的关系
线程发生OutOfMemoryError,首先是堆空间不够了,然后再由jvm在申请分配空间的的时候,在调用上抛出OOM异常。
申请内存的线程,会会像处理普通的其他异常一样,处理OutOfMemoryError。
线程是资源调度的基本单位,Java在设计线程时充分考虑了线程的独立性。
在异常方面,线程也保持了线程异常的独立性。
在线程执行中,如果发生的异常,都由线程进行独立的处理,而不是也不会抛出到其它的线程。这就是保证了这种线程的独立性。
从线程的实现维度,也可以看到异常处理的策略。
线程Thread里边,最终会执行内部target对象的run方法,也就是java.lang.Runnable接口实现方法,线程通过其run方法运行,方法签名如下:
public abstract void run();
注意这个方法,run方法不能声明抛出任何检查异常(checked exception)。因此在线程方法执行中发生的任何检查异常,必须在线程中处理。
线程拿到异常,有两种处理方式:
如果没有被捕获
除了检查异常,java中还有非检查异常(unchecked exception),这种异常无需显式声明也能沿着方法调用链向上抛出。
线程对于这种未处理的异常,提供了默认异常处理器:
/*** Dispatch an uncaught exception to the handler. This method is* intended to be called only by the JVM. (将未被捕获的异常分发给处理器。这个方法只被JVM调用)*/private void dispatchUncaughtException(Throwable e) { getUncaughtExceptionHandler().uncaughtException(this, e);}
Thread的init()方法线程至少有一个默认异常处理器,兜底的异常处理器是当前线程父线程的线程组ThreadGroup,可以看到线程组是有能力处理异常的:
public class ThreadGroup implements Thread.UncaughtExceptionHandler {}
线程通过这两种机制,保证内部发生的异常,在线程内解决,而不会抛出给启动线程的外部线程。
java虚拟机退出的条件是:JVM 不存在非守护线程(前台线程),JVM就会退出。
线程发生未处理的异常(未处理异常由默认异常处理器处理)会导致线程结束,而线程结束了, 如果还有非守护线程(前台线程),JVM也不会退出。
OOM也是一种异常,它的发生也不会导致JVM退出。
所以,OOM 与JVM的退出,没有很强的关系。
以下实例说明:
实例一:线程OOM,JVM不一定退出
实例二:线程池OOM,JVM不一定退出
thread-0 线程抛出OOM 后线程结束后,main线程依旧会循环打印"我还行....."。
线程中发生OOM异常,和发生其他异常一样,只是那个线程终止了,但是不影响其他线程,
thread-0 线程线程OOM,也不会导致JVM退出。
class OOMThreadPool { private final Byte[] toLeak; public OOMThreadPool() { toLeak = new Byte[1024 * 1024]; } static final Thread[] t = new Thread[1]; static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(9), new ThreadFactory() { public Thread newThread(Runnable r) { t[0] = new Thread(r); t[0].setDaemon(false); t[0].setPriority(Thread.NORM_PRIORITY); t[0].setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { e.printStackTrace(); System.out.println(t.getName() + " 的状态:" + t.getState()); System.out.println("这里是没有捕获的处理 ====> " + t.getId() + "==> " + e.getLocalizedMessage()); } }); return t[0]; } }, new ThreadPoolExecutor.DiscardOldestPolicy()) { @Override protected void afterExecute(Runnable r, Throwable t) { System.out.println(Thread.currentThread().getName() + " 任务执行完成,但是线程不会结束"); if (null != t) { System.out.println(Thread.currentThread().getName() + "任务异常了"); t.printStackTrace(); } } }; // 为快速发生oom,设置堆大小; VM args: -Xms10m -Xmx10m public static void main(String[] args) throws InterruptedException { List list = new LinkedList<>(); Runnable target = () -> { System.out.println(Thread.currentThread().getName() + " 开始了"); try { while (true) { list.add(new OOMThreadPool()); } }catch ( Throwable throwable) { throwable.printStackTrace(); } }; threadPool.submit(target); while (true) { System.out.println(Thread.currentThread().getName() + " 我还行..."); System.out.println(t[0].getName() + " 的状态:" + t[0].getState()); Thread.sleep(1000L); } }}
在线程池中,thread-0 线程抛出OOM 后线程结束后,main线程依旧会循环打印"我还行....."。
在线程池中,thread-0 线程 的任务结束了, 但是 线程没有结束,还是可以执行新任务的。
什么时候发生OOM、JVM才退出呢?
OOM的发生表示了此刻JVM堆内存告罄,不能分配出更多的资源,或者GC回收效率不可观。
一个线程的OOM,在一定程度的并发下,若此时其他线程(含非守护线程)也需要申请堆内存,那么其他线程也会因为申请不到内存而OOM,甚至连锁反应导致整个JVM的退出。
Linux 内核有个机制叫OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大,尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉。内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码linux/mm/oom_kill.c,当系统内存不足的时候,out_of_memory()被触发,然后调用select_bad_process()选择一个”bad”进程杀掉。如何判断和选择一个”bad进程呢?linux选择”bad”进程是通过调用oom_badness(),挑选的算法和想法都很简单很朴实:最bad的那个进程就是那个最占用内存的进程。
OOM相关面试题,是非常常见的面试题。
以上的5大方案,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。
最终,让面试官爱到 “不能自已、口水直流”。 offer, 也就来了。
学习过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。
《响应式圣经:10W字,实现Spring响应式编程自由》
这是老版本 《Flux、Mono、Reactor 实战(史上最全)》
《Spring cloud Alibaba 学习圣经》 PDF
《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》
《Linux命令大全:2W多字,一次实现Linux自由》
《TCP协议详解 (史上最全)》
《网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!》
《Redis分布式锁(图解 - 秒懂 - 史上最全)》
《Zookeeper 分布式锁 - 图解 - 秒懂》
《队列之王: Disruptor 原理、架构、源码 一文穿透》
《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
《缓存之王:Caffeine 的使用(史上最全)》
《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》
4000页《尼恩Java面试宝典 》 40个专题
Copyright © 2015-2022 南非时尚网版权所有 备案号:沪ICP备2022005074号-13 联系邮箱:58 55 97 3@qq.com