前言
本篇记录一些常见问题
1:常见的OOM场景有哪些?
A:java.lang.OutOfMemoryError: Java heap space
堆空间不足以给进入老年代的对象分配空间,可以通过Xmx调整大小解决。
新生对象在Eden区分配内存,Eden区满了之后YGC会把Eden区存活的对象放到Survivor区,Survivor区分为From和To,每次GC都会检查Eden和From把存活的复制到To然后From和To换名称。当Eden和Survivor都无法分配或者对象已经成功躲过几轮YGC的时候对象会被处理到老年代,下面的例子一直创建对象,占满新生代之后请求在老年代分配对象空间,如果老年代空间也不够了就会抛出这个错误!
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
B:java.lang.OutOfMemoryError: PermGen space
堆空间无法给新的类的Meta信息和类的Class对象分配空间,可通过调整-XX:MaxPermSize大小解决。
一般Spring动态代理生成过多的字节码类或者有大量的类被加载进入内存的时候会出现这个错误,JSP预编译的时候也会出现这个错误!错误的主要原因是YGC和FGC都不会堆永久代进行垃圾清理。
C:java.lang.StackOverflowError
线程请求的栈深度大于虚拟机分配给线程的栈空间和方法栈的大小大于线程栈空间的时候会出现这个错误!
虚拟机栈和本地方法栈是线程独立的内存,外加一个程序计数器,每当新建线程的时候JVM都会给线程分配这三个内存作为栈空间。
无限递归就会出现这个问题
public class StackOverflowErrorMain {
int stackLength = 0;
public StackOverflowErrorMain() {
}
public void addStackLength(){
stackLength++;
addStackLength();
}
public static void main(String[] args){
StackOverflowErrorMain sofem = new StackOverflowErrorMain();
try {
sofem.addStackLength();
}catch (Throwable e){
System.out.println(sofem.stackLength);
e.printStackTrace();
}
}
}
D:OutOfMemoryError: unable to create new native thread
无法给新的线程分配栈空间就会出现这个问题!
一直创建线程可以复现。
E:OutOfMemoryError: GC overhead limit exceeded
通过统计GC时间来预测是否要OOM了,提前抛出异常,防止OOM发生,可以通过调整Xmx来解决问题。
GC的对象过多的时候JVM预测GC的时间会很长,98%的时间用来回收2%内存,这个时候就会抛出异常。
F:OutOfMemoryError:Requested array size exceeds VM limit
数组过大,请求分配空间的时候堆内存不足以满足,检查是不是数组size过大或者提高Xmx堆内存大小解决这个问题
G:OutOfMemoryErr java.io.FileInputStream.readBytes(Native Method)
堆内存分配的过多,占用了总内存的80%以上导致本地内存没有空间了
H:OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?
其他进程耗尽内存的时候会出现这个错误。
2、MinorGC和FullGC的触发条件
A:MinorGC触发条件
- Eden区满的时候
B:FullGC触发条件
- System.gc方法,建议但不一定触发
- 老年代空间不足的时候会触发
- 方法区空间不足的时候
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
- 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
3、Java有内存泄漏吗?什么情况内存泄漏?
A:Java中有内存泄漏
内存泄漏是指无用对象得不到GC的及时回收导致长时间占用内存,从而造成内存空间的浪费。
一般场景是一个长生命周期的对象持有短生命周期对象的引用
- 静态集合类引起,如HashMap等,这些静态变量生命周期于应用程序一致,他们引用的对象不能被释放
- 当集合里面的对象属性被修改后,remove()方法不起作用
- 各种链接没有显示关闭,如数据库和网络,IO链接等,不关闭GC不回收
- 单例模式对象持有外部对象的引用,外部对象无法被回收
- 内部类和外部模块等的引用
- 监听器,释放对象的时候忘记删除监听器,增加了内存泄漏的机会
B:可达性分析:
对象如果到GCRoot没有任何引用连相连的时候,说明这个对象可回收,回收过程分为两次,第一次筛选出覆盖finalize方法的对象放到F-Queue中,虚拟机会调用线程执行这个方法,如果F-Queue中第二次被标记,那么第二次垃圾回收的时候就会回收掉。可以作为GCRoot的对象有
- 虚拟机栈中引用的对象
- 方法类静态属性引用的对象
- 方法区常量池引用的对象(final等)
- 本地方法栈JNI引用的对象
4、TCP三次握手过程
A:首先了解为什么要进行三次握手?
为了客户端和服务器确保自己有接收信息和发送信息的能力
B:三次握手过程
客户端C发送链接请求到服务端,此时C不知道自己有没有收发能力
服务端S接收到C的连接请求并返回一个消息,此时S知道自己有接收消息的能力,不知道自己发送消息的能力
客户端接收到服务端的响应,此时客户端知道自己有发送消息和接收消息的能力,客户端发送确认指令给服务端
服务端接收到客户端响应信息,确认了自己发送信息的能力,至此三次握手成功确认了双方收发信息的能力。
C:三次握手重要概念
- 序号:seq序号32位,标识从TCP源端向目的端发送的字节流
-
确认序号:ack序号32位,只有ACK标志位为1的时候确认序号字段才有效,ack=seq+1
标志位:URG紧急指针有效、ACK确认序号有效、PSH接收方应尽快把这个报文交给应用层、RST重置连接、SYN发起一个新连接、FIN释放一个连接