不一定,要看是否还有存活的线程,如果有则 JVM 进程不会退出,否则才会退出
JVM 进程什么时候退出
需要明确,程序是否退出和发生 OOM 无关,而和当前是否还有存活的非守护线程有关。
只要还有运行中的子线程,即使 main 线程结束或异常崩溃了,程序也不会停止。
public class TestThreadRun {
private static class MyTask implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(String.format("我是子线程【%s】 每秒执行一次", Thread.currentThread().getName()));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyTask(), "sub-thread-1");
// thread.setDaemon(true);
thread.start();
System.out.println("main 线程结束");
throw new RuntimeException("抛个异常");
}
}
发生 OOM 时线程及其持有资源发生什么
当线程创建对象时,如果内存不足,会进行 GC,若GC 后还是内存不足,JVM 就会抛出OutOfMemoryError
,如果线程没有处理异常,异常会继续向外层抛出,kill 掉线程,线程本身和其持有的资源就可以被回收了。
OOM 异常只会导致当前线程结束,对其他线程无影响。
后续其他线程再申请内存空间时,可能由于空间不足会发起 GC,然后就能够回收掉被关闭的线程和相关资源。
但是,线程被 kill 掉后,线程持有的资源只是可以被回收了,并不是一定能够回收,如果这些资源被其他线程持有,或者是一些全局变量、静态变量等不容易被 GC 的对象,那么堆内存空间占用依然会比较高。
这时候,其他线程再申请内存时,会继续发生 OOM 直至所有线程都结束,程序退出。
模拟 OOM 虚拟机配置:
-Xmx50M
-Xms50M
-XX:+HeapDumpOnOutOfMemoryError
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:HeapDumpPath=/data/oom/dump/
a) 资源可以被回收的情况
测试代码:
public class TestThreadOOM {
private static class Task1 implements Runnable {
List<byte[]> list = new ArrayList<>(); // 资源与线程绑定
@Override
public void run() {
while (true) {
System.out.println(String.format("我是子线程【%s】 每秒创建一些字节数组", Thread.currentThread().getName()));
list.add(new byte[1024*1024*5]);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private static class Task2 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(String.format("我是子线程【%s】 每秒执行一次", Thread.currentThread().getName()));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Task1(), "thread-1");
thread.start();
Thread thread2 = new Thread(new Task2(), "thread-2");
thread2.start();
thread.join();
thread2.join();
System.out.println("main 线程结束");
}
}
根据时间线,可以看到,当线程发送 OOM 后,资源并没有立即被回收掉,而是下次 GC 时回收
b) 资源回收不了的情况
代码和上面差不多,只是 List 调整为静态变量
public class TestThreadOOM {
static List<byte[]> list = new ArrayList<>(); // 使用静态变量 不会被回收
private static class Task1 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(String.format("我是子线程【%s】 每秒创建一些字节数组", Thread.currentThread().getName()));
list.add(new byte[1024*1024*5]);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private static class Task2 implements Runnable {
private AtomicInteger i = new AtomicInteger(0);
@Override
public void run() {
while (true) {
System.out.println(String.format("我是子线程【%s】 每秒执行一次", Thread.currentThread().getName()));
if (i.getAndIncrement() >= 10) { // 确保线程1 oom了
byte[] buf = new byte[1024*1024 * 20]; // 申请一些内存
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Task1(), "thread-1");
thread.start();
Thread thread2 = new Thread(new Task2(), "thread-2");
thread2.start();
thread.join();
thread2.join();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("【%s】申请内存", Thread.currentThread().getName()));
byte[] buf = new byte[1024*1024 * 20]; // 申请一些内存
System.out.println("main 线程结束");
}
}
最后补充,OOM 异常是可以被捕获的,捕获异常后可以尝试继续执行
原创文章,作者:6024010,如若转载,请注明出处:https://blog.ytso.com/270592.html