JVM
一、Jvm概述
根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括如上几个运行时数据区域(就是上图的内存区域)。
1.1程序计数器:
程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。每个线程的计数器都是独立的,互不影响,所以这片内存为线程私有。不会产生OOM
1.2 Java虚拟机栈
Java虚拟机栈也是线程私有的,每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈出栈的过程。通过-Xss控制。会产生OOM,还是两种:
1. 如果线程请求的栈深度超过虚拟机允许的最大深度,将抛出StackOverFlowError (栈帧过多,或者线程数太多)
2. 如果栈内存过小,申请空间得不到满足时将抛出OutOfMemoryError,-Xss控制
1.3本地方法栈
本地方法栈与虚拟机栈作用类似,只不过虚拟机栈为虚拟机执行Java方法服务,本地方法栈为Native方法服务。(HotSpot直接将Java虚拟栈和本地方法栈合并了),会OOM。
1.4 Java堆
Java堆是JVM管理的内存中最大的,被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。为了更好地进行垃圾回收,堆内存被分为:新生代和年老代;更详细一点的是:Eden区、From Survivor区,To Survivor区,Tenured Space。记住一点:无论怎么划分,都与存放的内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或更快地分配内存。通过-Xms –Xmx控制。主要的OOM就是发生在这块区域。
1.5 方法区
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。这个区域经常会混淆,Java虚拟机规范把方法区描述为堆的一个逻辑部分;而HotSpot虚拟机设计团队用永久代实现了方法区,并把垃圾回收扩展到了永久代(一般的垃圾回收都是对堆内存的回收,毕竟是为了回收对象,而对象都是创建在堆上的),弄的大家对方法区和持久代非常混淆,可以这么说:方法区是概念上的一个东东,持久代是多数虚拟机实现方式中最热门的(HotSpot)实现方式而已。如果实在想较真儿,那还是用方法区的别名来说明吧:非堆区(Non-Heap)。当然也会OOM。
1.6 运行时常量池
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。之所以单独提出来这个东西是因为运行时常量池相对于Class文件常量池的另外一个重要特性是具备动态性,Java语言并不要求常量一定只能在编译期产生,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String的intern方法。会OOM,HotSpot直接作为持久代的一部分。
1.7直接内存
直接内存不是JVM运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,由于也可以导致OOM,所以讲一下。NIO中,缓冲区可以通过使用native函数库直接分配堆外内存,这样如果堆外内存不够的话就会产生OOM。
从上可知,只有程序计数器不会发生OOM,其他的区域都会发生OOM。
二、HotSpot
下面我们看下HotSpot的内存:
可见,Hot Spot的Jvm从总体上分为堆和非堆。
图一:(注意非堆)
图二:(注意非堆)
如图所示:
堆:
EdenSpace:伊甸区 new对象时最先分配的区域
SurvivorSpace:幸存者区保存young GC之后存活的对象(应该有两块儿,方便Swap)
Tenured Space:年老代 经过MaxTenuringThreashold(默认15) Minor GC之后的对象进入年老代
非堆:
CodeCache:代码缓存区:用于编译和保存本地代码的内存(HotSpot特有)
PermGen:持久代 用于存放类信息、常量、静态变量,如类名、访问修饰符、常量池、字段描述、方法描述等。
PermGen [shared-rw]:类文件共享区(读写)
Perm Gen [shared-ro]:类文件共享区(只读)
Class data sharing (CDS)(http://java.sun.com/j2se/1.5.0/docs/guide/vm/class-data-sharing.html)是JDK5新引入的特性,采用在虚拟机之间共享一些class定义信息(bootstrapClassLoader加载的类)的方式提速JVM的启动和内存的占用,主要用于客户端,如果需要对类进行instrutment,最好把CDS关闭,关闭之后就变成了图二
-Xshare:off |
三、几种OOM的实例
3.1堆内存OOM
通过调整堆内存大小,使new对象时对象申请空间不足,导致OOM
package com.ying.jvm;
import java.util.ArrayList; import java.util.List;
/** * Created by IntelliJ IDEA. * User: yingkh * Date: 13-11-21 * Time: 下午9:15 * -Xshare:off -Xss512k -Xms10m -Xmx10m * 堆内存溢出 * To change this template use File | Settings | File Templates. */ public class JavaHeapSpaceTest { public static void main(String args[]) { try { Thread.sleep(8000); //为了方便链接JConsole,先睡眠几秒 } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } List<User> list = new ArrayList<User>(); int i = 0; while (true) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } list.add(new User("zhangsan" + i++, "boy")); } }
static class User { private String name; private String sex;
User(String name, String sex) { this.name = name; this.sex = sex; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; } } } |
结果:
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space at java.lang.StringBuilder.toString(StringBuilder.java:430) at com.ying.jvm.JavaHeapSpaceTest$1.run(JavaHeapSpaceTest.java:20) at java.lang.Thread.run(Thread.java:619) |
如图:
3.2栈内存OOM
3.2.1StackOverFlow:
如果线程请求的栈深度超过虚拟机允许的最大深度,将抛出StackOverFlowError (栈帧过多,或者线程数太多)
package com.ying.jvm;
/** * Created by IntelliJ IDEA. * User: yingkh * Date: 13-11-21 * Time: 下午9:25 * 栈溢出: * 1. 递归方法增加栈帧深度,导致达到虚拟机允许的最大深度,抛出StackOverFlow异常 * 2. 调整-Xss参数,导致栈的容量不够,抛出OOM ,但是试验之后还是抛出 StackOverFlow异常 * To change this template use File | Settings | File Templates. */ public class StackOverFlowTest { int i = 0;
public static void main(String args[]) { StackOverFlowTest stackOverFlowTest = new StackOverFlowTest(); stackOverFlowTest.stack(); }
/** * 递归调用,直至超过栈帧数量 */ private void stack() { i++; stack(); }
/** * 增加许多变量来占用栈的内存 * -Xshare:off -Xss1k * 设置栈大小为1k,但是最后没反应,还是StackOverFlow */ private void stack2() { i++; long j=0l; long k=1l; long l=2l; long n=4l; for(int m=0;m<1024*4;m++) { j=j+k; j=j+k; j=j+k; j=j+k; j=j+k; } if(i==10) { return ; } stack2(); } } |
结果:
Exception in thread "main" java.lang.StackOverflowError at com.ying.jvm.StackOverFlowTest.stack(StackOverFlowTest.java:26) at com.ying.jvm.StackOverFlowTest.stack(StackOverFlowTest.java:26) |
3.2.2OOM:
如果栈内存过小,申请空间得不到满足时将抛出OutOfMemoryError,-Xss控制
package com.ying.jvm;
/** * Created by IntelliJ IDEA. * User: yingkh * Date: 13-11-21 * Time: 下午9:34 * -Xshare:off -Xss512k -Xms1574m -Xmx1574m //堆内存的大小也是尝试了数次得到的 * Jvm可创建线程的数量=(物理内存-堆内存-持久代内存-OS的一些内存)/线程大小 * 物理内存剩余的内存就是栈内存可以申请的大小,如果剩下的内存能生成100个线程,则生成第101个时候 * 就相当于栈空间不够了,这时就会报OOM,这样我们就变相地达到了得到 栈内存不足导致的第二个问题, * 而不会产生StackOverFlow * * To change this template use File | Settings | File Templates. */ public class StackOOMTest { public static void main(String args[]) { int threadCout = 5000; //这个数量灵活变动,我是根据本机的内存尝试的 for (int i = 0; i < threadCout; i++) { Thread thread = new Thread(new Runnable() { public void run() { // System.out.println("创建线程ok"); } }); thread.start(); }
} } |
结果:
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:597) at com.ying.jvm.StackOOMTest.main(StackOOMTest.java:25) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) |
3.3 Perm Gen:
package com.ying.jvm;
import java.util.ArrayList; import java.util.List;
/** * Created by IntelliJ IDEA. * User: yingkh * Date: 13-11-21 * Time: 下午7:28 * -XX:PermSize=10M -XX:MaxPermSize=10M -Xshare:off * 持久代 * To change this template use File | Settings | File Templates. */ public class RuntimeConstantPoolOOm { public static void main(String args[]) { List<String> list = new ArrayList<String>(); int i = 0; while (true) { try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } list.add(String.valueOf(i++).intern()); //String的intern会利用运行时常量池,运行时常量池是方法区中的一段区域,当池中有一个等于此String对象的字符串 // 时,就返回代表这个字符串的String对象,如果没有,则将此String对象包含的字符串添加到常量池中,并返回此String对象 // 的引用。 } } } |
执行结果:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) at com.ying.jvm.RuntimeConstantPoolOOm.main(RuntimeConstantPoolOOm.java:26) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) |
如图所示,持久代内存直线上升,直至OOM。