JVM学习2

  • 1、OOM的概念
  • 2、JVM堆内存溢出
  • 3、JVM栈/Native栈内存溢出
  • 4、方法区与常量池的内存溢出
  • 5、本机直接内存溢出

1、OOM的概念

OOM便是OutOfMemroyError,它是一个异常错误,代表的意思便是JVM当中的内存不够用了,最终导致程序的Crash,这个问题不是有某一类的特定语法导致的,存在很多的情况,所以这种错误对于程序员来说还是相当的难解的,因为我们的内存回收工作是交给GC去做的,具体如何回收,我们不清楚,具体是JVM当中的哪一个区域发生的内存溢出,我们可以通过Log去分析,但是分析起来还是很费劲的,尤其是大型的系统,这便是OOM对程序员造成的痛。

2、JVM堆内存溢出

我们知道JVM堆当中存储的便是对象实例,那么只要我们不断的创建对象,并且保证GC Roots到对象之间有可达路径,用来避免垃圾回收机制清除这些对象,那么只要我们不断的new出新的对象,那么JVM虚拟机必然会出现内存溢出的异常。

这里通过三个参数来控制JVM堆内存的大小变化分别是

  • Xsm:堆的最小值参数
  • -Xmx:堆的最大值参数
  • -XX: +HeapDumpOnOutOfMemoryError:可以让虚拟机在出现内存溢出异常时,Dump出当前的内存堆转储快照以便事后分析

在Eclipse的Debug/Run页面,将堆的最小值和最大值都设置为20m,这样可以避免堆自动扩展。当我们设置完上面的参数值以后,我们通过一个Demo来看一下,如何造成堆内存溢出。

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
26

* 探究JVM 堆内存溢出,堆当中存储的是对象的实例,只要我们不断的new对象实例 就OK
* @author pengchengliu
* JVM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*
*/
public class {
static class OOMObject {}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid795.hprof ...
Heap dump file created [27575251 bytes in 0.114 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at outofmemoryerror.HeapOOM.main(HeapOOM.java:19)

当JVM堆当中出现OOM的异常还是比较常见的,当JVM当中出现堆内存溢出的时候。控制台上面便会抛出OOM异常,紧接着便会在后面出现 Java heap space 这样的字样,如果出现了这样的字样的时候,便可以得出当前的JVM堆现在状态便是内存溢出的状态。那么关于JVM堆内存溢出我们应该如何解决呢?

分析

当这个区域当中出现了相应的OOM异常的时候,一般的手段便是先通过内存映像分析工具,对刚刚Dump出来的堆内存情况的快照进行分析,重点便是先确定内存当中的对象是否是必要的,也就是先分析清楚是内存泄露还是内存溢出。

  • 1、内存泄漏:如果可以确定是内存泄漏,可以进一步通过工具查看泄漏对象到GC Root的引用链,于是就能找到泄漏到泄漏对象是通过怎么的路径与GC Root相关联并且导致GC无法自动回收的,如果掌握了泄漏对象的类型信息以及GC root引用链的信息,就可以快速定位到泄漏代码的位置。

  • 2、内存溢出:如果不是内存泄漏导致的,那就是纯粹的对象实例过多导致的,那么我们的第一步便是确定当前内存当中的所有对象是是否必须活着,如果不是,我们可以更改相应对象实例到GC Root的引用链的类型,如果是那就需要检查堆大小参数 -Xms -Xmx ,与物理内存进行对比看能否加大内存,从代码上面检查是否存在某些对象生命周期是否过长,持有的时间是否过长,减少程序运行期间内存的消耗。

3、JVM 栈 / Native 栈 内存溢出

首先这个区域JVM规范当中定义了两种内存溢出的异常

  • 如果线程请求的栈的深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常 (例如:错误的递归)
  • 如果虚拟机在扩展时,无法申请到足够的内存空间,将抛出OOM异常 (例如:开启无限线程)

我们可以通过 -Xoss 参数设置Native 栈的大小,通过-Xss 参数设置JVM栈的大小,但是在JVM众多的实现当中,有的虚拟机将上面两种栈结构通过了一种方式进行实现,导致使用-Xoss参数无法生效,那就只能使用-Xss进行设置。

下面通过一个Demo来演示想要的JVM栈内存溢出

1
大专栏  JVM学习2 - OutOfMemroyError***ne">2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

* 探究JVM栈深度OOM溢出
* JVM args : -Xss160k
* @author pengchengliu
*
*/
public class JavaStackOOM01 {
private int stackLength = 1 ;
public void stackLeak() {
stackLength ++ ;
stackLeak();
}
public static void main(String[] args) {
JavaStackOOM01 javaStackOOM01 = new JavaStackOOM01();
try {
javaStackOOM01.stackLeak();
} catch (Throwable e) {
System.out.println("stack lenth :" + javaStackOOM01.stackLength);
throw e ;
}
}
}
stack lenth :771
Exception in thread "main" java.lang.StackOverflowError
at outofmemoryerror.JavaStackOOM01.stackLeak(JavaStackOOM01.java:14)
at outofmemoryerror.JavaStackOOM01.stackLeak(JavaStackOOM01.java:15)
at outofmemoryerror.JavaStackOOM01.stackLeak(JavaStackOOM01.java:15)
at outofmemoryerror.JavaStackOOM01.stackLeak(JavaStackOOM01.java:15)
at outofmemoryerror.JavaStackOOM01.stackLeak(JavaStackOOM01.java:15)

at outofmemoryerror.JavaStackOOM01.main(JavaStackOOM01.java:21)

如果我们使用默认的JVM栈的大小,那么递归的深度达到1000~2000是没有任何问题的,可以满足大多数情况下的递归调用,所以这种模式下面出现的溢出异常,我们可以不必要的进行深究。

下面演示一个通过线程数开辟栈的个数上线的时候出现的OOM异常

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
26

* 探究JVM栈大小OOM溢出
* JVM args : -Xss2m
* @author pengchengliu
*
*/
public class JavaStackOOM02 {
private void dontStop () {
while (true) {}
}
public void stackLeakByThread () {
while (true) {
Thread thread = new Thread(new Runnable(){

public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
JavaStackOOM02 javaStackOOM02 = new JavaStackOOM02();
javaStackOOM02.stackLeakByThread();
}
}

上述的这种做法还是会出现OOM异常的,这种方式在我们的日常开发当中还是比较常用的,如果遇到这种OOM,如果我们不能减少线程的数量活着更改64为虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

4、方法区与常量池的内存溢出

备注:剩下几种情况。 还未遇到, 遇到以后。第一时间更新

原文地址:https://www.cnblogs.com/lijianming180/p/12409279.html