盒子
盒子
文章目录
  1. 1. java堆内存溢出
  2. 2. java堆内存泄漏
  3. 3. 垃圾回收超时内存溢出
  4. 4. Metaspace内存溢出
  5. 5. 直接内存溢出
  6. 6. 栈内存溢出
  7. 7. 创建本地线程内存溢出
  8. 8. 超出交换区内存溢出
  9. 9. 数组超限内存溢出
  10. 10. 系统杀死进程内存溢出

内存溢出常见场景

内存溢出分为两大类:OutOfMemoryError和StackOverflowError

1. java堆内存溢出

当出现java.lang.OutOfMemeoryError:Java heap space异常时,就是堆内存溢出了

问题描述:

  1. 设置的JVM内存太小,对象所需内存过大,创建对象时分配空间,就会抛出这个异常
  2. 流量/数据峰值超过预期阈值时

示例代码: 执行时jvm参数设置为-Xms20m -Xmx20m

1
2
3
4
5
6
7
8
9
public class HeapOomError {

public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[5 * 1024 * 1024]);
}
}
}

执行结果:
HeapOomError

解决方法:

  1. 如果没有什么问题,可以适当调整-Xms -Xmx参数,压测调整到最优值
  2. 尽量避免大对象申请,像文件上传、大批量从数据库读取,尽量分块或者分批处理

2. java堆内存泄漏

问题描述:
内存泄漏是一些对象不再被应用程序使用,但是垃圾收集器无法识别的情况

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MemoryLeakOomError {

public static void main(String[] args) {
Map map = new HashMap();
while (true) {
for (int i = 0; i < 10000; i++) {
Key key = new Key(i);
if (!map.containsKey(key)) {
map.put(key, i);
}
}
System.out.println(map.size());
}
}
}

class Key {
Integer id;

public Key(Integer id) {
this.id = id;
}
}

执行结果:
MemoryLeakOomError

解决方法:
重写hashCode()、equals()方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Key {
Integer id;

public Key(Integer id) {
this.id = id;
}

@Override
public int hashCode() {
return id.hashCode();
}

@Override
public boolean equals(Object obj) {
boolean res = false;
if (obj instanceof Key) {
res = ((Key)obj).id.equals(this.id);
}
return res;
}
}

3. 垃圾回收超时内存溢出

问题描述:
默认JVM配置GC的时间超过98%,回收堆内存低于2%

示例代码:

1
2
3
4
5
6
7
8
9
10
public class OverheadLimitOomError {

public static void main(String[] args) {
Map map = System.getProperties();
Random r = new Random();
while (true) {
map.put(r.nextInt(), "Hello World");
}
}
}

执行结果:
OverheadLimitOomError

解决方法:
减少对象生命周期,尽量能快速的进行垃圾回收
-XX:-UseGCOverheadLimit取消GC开销限制

4. Metaspace内存溢出

问题描述:
元空间的溢出,系统会抛出java.lang.OutofMemoryError:Metaspace,出现这个问题是系统引用第三方包或者动态代码生成类加载等,导致元空间溢出

示例代码: 执行时设置参数-XX:MaxMetaspaceSize=6m -XX:+PrintGCDetails

1
2
3
public interface UserService {
String getUsername();
}

1
2
3
4
5
6
public class UserServiceImpl implements UserService {
@Override
public String getUsername() {
return "Domi";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class UserInvocationHandler implements InvocationHandler {
private Object target;

public UserInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MetaspaceOomError {

private static Map<Integer, UserService> map = new HashMap<>(); // 静态属性引用的对象存在于元空间

public static void main(String[] args) throws MalformedURLException {
ClassLoadingMXBean loadingMXBean = ManagementFactory.getClassLoadingMXBean();
LongAdder longAdder = new LongAdder();
while (true) {
URL[] urls = new URL[] { new URL("file:" + longAdder.intValue() + ".jar") };
URLClassLoader urlClassLoader = new URLClassLoader(urls); // 关键在于每次创建新的classloader
UserService userService = new UserServiceImpl();
// 若把urlClassLoader参数改为userService.getClass().getClassLoader()则不会出现元空间溢出情况
UserService proxy = (UserService) Proxy.newProxyInstance(urlClassLoader,
userService.getClass().getInterfaces(), new UserInvocationHandler(userService));
userService.getClass().getInterfaces(), new UserInvocationHandler(userService));
map.put(longAdder.intValue(), proxy);
longAdder.increment();
System.out.print("total: " + loadingMXBean.getTotalLoadedClassCount() +
" loaded: " + loadingMXBean.getLoadedClassCount() +
" unloaded: " + loadingMXBean.getUnloadedClassCount() +
" map size:" + map.size() + "\n");

}
}
}

执行结果:
MetaspaceOomError

解决方法:
默认情况下,元空间大小仅受本地内存的影响
1)优化-XX:MetaspaceSize、-XX:MaxMetaspaceSize
2)慎重引用第三方包
3)关注动态生成类的框架

5. 直接内存溢出

问题描述:
直接内存溢出,系统抛出java.lang.OutOfMemoryError: Direct buffer memory。
如果直接或间接使用了ByteBuffer#allocateDirect方法的时候,而不做clear的时候就会出现类似的问题

示例代码:-XX:+PrintGCDetails -XX:MaxDirectMemorySize=10M

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DirectoryMemoryOomError {

private static final int SIZE = 1024 * 1024;
private static Map<String, ByteBuffer> map = new HashMap<>();

public static void main(String[] args) {
int i = 0;
while (true) {
ByteBuffer buffer = ByteBuffer.allocateDirect(SIZE);
map.put(" " + (i++), buffer);
}
}
}

执行结果:
DirectoryMemoryOomError

解决方法:
可以考虑设置参数:-XX:MaxDirectMemorySize,并及时clear内存

6. 栈内存溢出

问题描述:
栈内存溢出时,会抛出java.lang.StackOverflowError。方法的每一次调用都会产生一个栈帧,当递归调用自己死递归时,栈内存溢出。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
public class StackOomError {

public void call() {
this.call();
}

public static void main(String[] args) {
StackOomError stackOomError = new StackOomError();
stackOomError.call();
}
}

执行结果:
StackOomError

解决方法:
程序中确实存在递归,可适当调大-Xss的大小,同时也要防止死递归

7. 创建本地线程内存溢出

问题描述:
线程基本占了heap以外的内存区域,要么是内存本身不够,要么heap空间设置的过大

示例代码:

1
2
3
4
5
6
7
8
9
10
11
public class UnableCreateNativeThreadError {

public static void main(String[] args) {
while (true) {
Executor executor = Executors.newCachedThreadPool();
executor.execute(() -> {
System.out.println("Hello World!");
});
}
}
}

执行结果:
UnableCreateNativeThreadError

解决方法:
尽量保证线程最大数可控制,不要随意用线程池
减少堆空间
修改操作系统级别的限制:

1
2
3
ulimit -a # 查看配置
ulimit -u 1800 # 增大用户最大进程数
ulimit -n 4096 # 增大每个进程可打开的最大文件数

减小-Xss栈空间

8. 超出交换区内存溢出

问题描述:
当JVM请求的总内存大于可用物理内存的时候,操作系统开始将数据从内存交换到硬盘

解决方法:
增加系统交换区的大小,但是性能会大大降低,生产环境避免最大内存超过系统物理内存,
其次,去掉系统交换区echo "vm.swappiness = 0" >> /etc/sysctl.conf

9. 数组超限内存溢出

问题描述:
数组超限内存溢出,系统报java.lang.OutOfMemoryError: Requested array size exceeds VM limit,数组分配长度要在平台允许范围之内

示例代码:

1
2
3
4
5
6
public class ArrayLimitOomError {

public static void main(String[] args) {
int[] arr = new int[Integer.MAX_VALUE - 1];
}
}

执行结果:
ArrayLimitOomError

解决方法:
数组长度要在平台允许的长度范围之内,比如Integer.MAX_VALUE - 2

10. 系统杀死进程内存溢出

进程在内核中作业,其中有个特殊的进程,称为“内存杀手”。当内核检测到系统内存不足时,OOM killer被激活,检查当前谁占用内存最大就杀死谁

解决方法:
增加系统内存

参考:十种JVM内存溢出的情况,你碰到过几种?
多次面试题目

支持一下
扫一扫,支持沈健
  • 微信扫一扫
  • 支付宝扫一扫