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();
}
}
}
创建大对象,大数组都会产生这个错误。
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
解决办法:
- 想办法降低应用程序创建的线程数量,分析应用是否真的需要创建这么多线程。如果不是,修改相关代码讲线程数降到最低。
- 对于有的应用,确实需要创建很多线程,远超过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();
}
}
}