java常见面试考点(三十):常见异常

java常见面试考点

往期文章推荐:
  java常见面试考点(二十五):CAS是什么
  java常见面试考点(二十六):集合类不安全
  java常见面试考点(二十七):java里的锁总结
  java常见面试考点(二十八):内部类详解
  java常见面试考点(二十九):进程和线程的区别


【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权);

本博客的内容来自于:java常见面试考点(三十):常见异常

学习、合作与交流联系q384660495;

本博客的内容仅供学习与参考,并非营利;


一、Error与Exception的区别

错误

要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。如:NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等。
  • 错误:Error类对象由 Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。

二、SOFE之StackOverflowError

栈溢出

堆栈溢出最常见的就是递归调用或者是循环调用了

package com.study.oom;

public class StackOverflowErrorDemo {
    public static void main(String[] args) {
        StackOverflowError();
    }

    private static void StackOverflowError() {
        // Exceprion in thread "main" java.lang.StackOverflowError
        StackOverflowError();
    }
}

三、OOM之OutOfMemory

1、OOM之Java heap space

package com.study.oom;

import java.util.Random;
//-Xms10m -Xmx10m
public class JavaHeapSpaceDemo {

    public static void main(String[] args) {
        String str = "weiwozongheng";
        while (true) {
            // Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
            str += str + new Random().nextInt(11111111) + new Random().nextInt(222222);
            str.intern();
        }
    }
}

创建大对象,大数组都会产生这个错误。
heap space

2、OOM之GC overhead linit exceeded

GC回收时间过长时会抛出OutOfMemoryError。

过长的定义是,超过99%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC都只是回收了不到2%的极端情况下才会抛出。 假如不抛出GC overhead limit 错误会发生什么情况呢? 那就是GC清理的这么点儿内存很快会被再次填满,迫使GC再次执行,这样就形成恶性循环。
CPU使用率一直是100%,而GC却没有任何成果。

package com.study.oom;
import java.util.LinkedList;
import java.util.List;

/**
 * JVM参数配置演示
 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 *
 * GC回收时间过长时会抛出OutOfMemoryError。
 * 过长的定义是,超过99%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC都只是回收了不到2%的极端情况下才会抛出。
 * 假如不抛出GC overhead limit 错误会发生什么情况呢?
 * 那就是GC清理的这么点儿内存很快会被再次填满,迫使GC再次执行,这样就形成恶性循环。
 * CPU使用率一直是100%,而GC却没有任何成果。
 */

public class GCOverheadDemo {
    public static void main(String[] args) {
        int i = 0;
        List<String> list = new LinkedList<String>();
        try {
            while (true) {
                list.add(String.valueOf(i++).intern());
            }
        } catch (Throwable throwable) {
            System.out.println("********** i = " + i);
            throwable.printStackTrace();
            throw throwable;
        }
    }
}

出现异常

java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.lang.Integer.toString(Integer.java:401)
	at java.lang.String.valueOf(String.java:3087)
	at com.study.oom.GCOverheadDemo.main(GCOverheadDemo.java:22)
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.lang.Integer.toString(Integer.java:401)
	at java.lang.String.valueOf(String.java:3087)
	at com.study.oom.GCOverheadDemo.main(GCOverheadDemo.java:22)

3、OOM之Direct buffer memory

写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式。
它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中唠会复制数据。

ByteBuffer.allocate(capability) 第一种方式:

  • 分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。

ByteBuffer.allocateDirect(capability) 第二种方式:

  • 分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。

但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收。这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了。

package com.study.oom;
import java.nio.ByteBuffer;

/**
 * 配置参数:
 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 *
 * 故障现象:
 * Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
 *
 * 导致原因:
 * 写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式。
 * 它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
 * 这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中唠会复制数据。
 *
 * ByteBuffer.allocate(capability) 第一种方式,分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。
 * ByteBuffer.allocateDirect(capability) 第二种方式, 分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快
 *
 * 但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收。
 * 这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了。
 */
public class DirectBufferMemoryDemo {

    public static void main(String[] args) {
        System.out.println("配置的maxDirectMemory: " + (sun.misc.VM.maxDirectMemory() / 1024 / 1024) + "MB");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
    }
}

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

4、OMM之unabe to create new native thread

翻译意思:不能再创建多个新的本地线程了,你的程序不管是微服务部署还是Linux系统,你目前创建的线程已经达到了,不能再创建了(大厂的高并发故障)

高并发请求服务器时,经常出现如下异常:

java.lang.OutOfMemoryError: unable to create new native thread

准确地讲,该native thread异常与对应的操作系统平台有关。

导致原因:

(1) 应用创建了太多的线程,一个应用进程创建多个线程,超过系统承载极限。

(2)服务器并不允许你的应用程序创建这么多的线程,linux系统默认允许单个进程创建的线程数是1024个。

应用创建线程超过这个数量,就会报java.lang.OutOfMemoryError: unable to create new native thread

解决办法:

  1. 想办法降低应用程序创建的线程数量,分析应用是否真的需要创建这么多线程。如果不是,修改相关代码讲线程数降到最低。
  2. 对于有的应用,确实需要创建很多线程,远超过linux系统默认的1024个线程的限制,可以通过修改linux服务器配置,扩大上限。

在Linux部署

注意:不要用root用户去操作

(1)在UnableCreateNewThreadDemo.java 文件拷贝到Linux环境中

[MrZhou@spark2 demo]$ ll
total 4
-rw-r--r--. 1 root root 469 Aug  1 17:33 UnableCreateNewThreadDemo.java

(2)由于带了包名,所以先编译

[MrZhou@spark2 demo]$ javac -d . UnableCreateNewThreadDemo.java 

(3)再次去查看,发现多了"com"的包名

[MrZhou@spark2 demo]$ ll
total 4
drwxr-xr-x. 3 root root  19 Aug  1 17:36 com
-rw-r--r--. 1 root root 469 Aug  1 17:33 UnableCreateNewThreadDemo.java

(4)运行

//运行
[MrZhou@spark2 demo]$ java  com.study.oom.UnableCreateNewThreadDemo

>>>>>>>>>> i = 1
>>>>>>>>>> i = 2
.................
.................
>>>>>>>>>> i = 4086

//不能再创建更多线程,并且这个ctrl+c不能强制退出的,需要杀进程才能退出
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
        at java.lang.Thread.start0(Native Method)
        at java.lang.Thread.start(Thread.java:717)
        at com.study.oom.UnableCreateNewThreadDemo.main(UnableCreateNewThreadDemo.java:15)

(5)查看进程&杀进程

//查看进程
[root@spark2 ~]# ps -ef|grep java
MrZhou    10171  10155  4 18:02 pts/0    00:00:04 java com.study.oom.UnableCreateNewThreadDemo
root      14304  14270  0 18:03 pts/1    00:00:00 grep --color=auto java
[root@spark2 ~]# 

//杀进程
[root@spark2 ~]# kill -9 10171
[root@spark2 ~]# 

//再查看启动的程序,发现已经退出了
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
        at java.lang.Thread.start0(Native Method)
        at java.lang.Thread.start(Thread.java:717)
        at com.study.oom.UnableCreateNewThreadDemo.main(UnableCreateNewThreadDemo.java:15)
Killed

OOM之unable to create new native thread上限调整
服务器调优参数

//查看用户默认是参数是多少  
[MrZhou@spark2 demo]$ ulimit -u   
1024  
//默认是1024 
//修改默认值
[MrZhou@spark2 demo]$ vim /etc/security/limits.d/20-nproc.conf

5、OOM之Metaspace

Java8及之后的版本使用Metaspace来替换永久代。

Metaspace是方法区在HotSpot中的实现,它与永久代最大的区别在于:Metaspace并不在也即在java8中,class metadata(the virtual machines internal presentation od java

class)虚拟机内存中而是使用本地内存,被存储在叫做Metaspace的native memory。

永久代(java8后被元空间(Metaspace)取代了)存放了以下信息:

虚拟机加载的类信息、 常量池、 静态变量、 即时编译后的代码

package com.study.oom;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * JVM参数:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m
 *
 * Java8及之后的版本使用Metaspace来替换永久代。
 *
 * Metaspace是方法区在HotSpot中的实现,它与永久代最大的区别在于:Metaspace并不在虚拟机内存中而是使用本地内存。
 * 也即在java8中,class metadata(the virtual machines internal presentation od java class)
 * 被存储在叫做Metaspace的native memory。
 *
 * 永久代(java8后被元空间(Metaspace)取代了)存放了以下信息:
 * 虚拟机加载的类信息
 * 常量池
 * 静态变量
 * 即时编译后的代码
 *
 * 模拟Metaspace空间溢出,我们不断生成类往元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的。
 */

public class MetaspaceDemo {
    static class OomTest {}

    public static void main(String[] args) {
        int i = 0;  //模拟计数多少次以后发生异常
        try {
            while (true) {
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OomTest.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor(){

                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o,args);
                    }
                });
                 enhancer.create();
            }
        }catch (Throwable e){
            System.out.println("***************多少次后发生异常:"+i);
            e.printStackTrace();
        }
    }
}

Metaspace

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏天的爱人是绿色

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值