jdk1.7.0_79 java
这张图我相信基本上对JVM有点接触的都应该很熟悉,能够说这是JVM入门的第一课。其中的“堆”和“虚拟机栈(栈)”更是耳熟能详。下面将围绕这张图对JVM的运行时数据区作一个简单介绍。程序员
程序计数器(Program Counter Register)数组
这和计算机操做系统中的程序计数器相似,在计算机操做系统中程序计数器表示这个进程要执行的下个指令的地址,对于JVM中的程序计数器能够看作是当前线程所执行的字节码的行号指示器,每一个线程都有一个程序计数器(这很好理解,每一个线程都有在执行任务,若是线程切换后要能保证能恢复到正确的位置),重要的一点——程序计数器,这是JVM规范中惟一一个没有规定会致使OutOfMemory(内存泄露,下文简称OOM)的区域。换句话上图中的其他4个区域,都有可能致使OOM。jvm
☆虚拟机栈(Java Virtual Machine Stacks)spa
这块内存区域就是咱们经常说的“栈”,咱们所熟知的是它用于存放变量,也就是说例如:操作系统
int i = 0;
虚拟机栈内存就会用4个字节来存储i变量。对于变量的内存空间是一开始就能肯定的(对于引用型变量,它固然存储的就是一个地址引用,其大小也是固定),因此这块内存区域在编译器就可以肯定下来,这块区域可能会抛出StackOverflowError或者OOM错误。设置JVM参数”-Xss228k”(栈大小为228k)。线程
1 package com.jvm; 2 3 /** 4 * -Xss228k,虚拟机栈大小为228k 5 * Created by yulinfeng on 7/11/17. 6 */ 7 public class Test { 8 private static int count = 0; 9 10 public static void main(String[] args) { 11 Test test = new Test(); 12 test.test(); 13 } 14 15 /** 16 * 递归调用 17 */ 18 private void test() { 19 try { 20 count++; 21 test(); 22 } catch (Throwable e) { //Exception已经捕获不了JVM抛出的StackOverflowError 23 System.out.println("递归调用次数" + count); 24 e.printStackTrace(); 25 } 26 } 27 }
这是一段没有终止条件的递归,执行结果以下图所示,JVM抛出StackOverflowError表示线程请求的栈深度大于JVM所容许的深度。3d
对于单线程状况下,不管如何抛出的都是StackOverflowError。若是要抛出OOM异常,致使的缘由是不断地在建立线程,直到将内存消耗殆尽。code
JVM的内存由堆内存 + 方法区内存 + 剩余内存,也就是剩余内存=操做系统分配给JVM的内存 - 堆内存 - 方法区内存。-Xss设置的是每一个线程的栈容量,也就是说能够建立的线程数量 = 剩余内存 / 栈内存。此时若是栈内存越大,能够建立的线程数量就少,就容易出现OOM;若是栈内存越小,能够建立的线程数量就多,就不容易出现OOM。对象
要避免这种状况最好就是减小堆内存+方法区内存,或者适当减小栈内存。对于栈内存的配置,通常采用默认值1M,或者采用64位操做系统以及64位的JVM。
本地方法栈(Native Method Stack)
本地方法栈和虚拟机栈相似,不一样的是虚拟机栈服务的是Java方法,而本地方法栈服务的是Native方法。在HotSpot虚拟机实现中是把本地方法栈和虚拟机栈合二为一的,同理它也会抛出StackOverflowError和OOM异常。
☆Java堆(Java Heap)
对于堆,Java程序员都知道对象实例以及数组内存都要在堆上分配。堆再也不被线程所独有而是共享的一块区域,它的确是用来存放对象实例,也是垃圾回收GC的主要区域。实际上它还能细分为:新生代(Young Generation)、老年代(Old Generation)。对于新生代又分为Eden空间、From Survivor空间、To Survivor空间。至于为何这么分,这涉及JVM的垃圾回收机制,在这里不作叙述。堆一样会抛出OOM异常,下面例子设置JVM参数” -Xms20M -Xmx20M“(前者表示初始堆大小20M,后者表示最大堆大小20M)。
1 package com.jvm; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * -Xms20M -Xmx20M 堆初始大小20M 堆最大大小20M 8 * Created by yulinfeng on 7/11/17. 9 */ 10 public class Test { 11 12 public static void main(String[] args) { 13 List<Test> list = new ArrayList<Test>(); 14 int count = 0; 15 try { 16 while (true) { 17 count++; 18 list.add(new Test()); //不断建立线程 19 } 20 } catch (Throwable e) { 21 System.out.println("建立实例个数:" + count); 22 e.printStackTrace(); 23 } 24 25 } 26 }
执行的结果能够清楚地看到堆上的内存空间溢出了。
☆方法区(Method Area)
对于JVM的方法区,可能听得最多的是另一个说法——永久代(Permanent Generation),呼应堆的新生代和老年代。方法区和堆的划分是JVM规范的定义,而不一样虚拟机有不一样实现,对于Hotspot虚拟机来讲,将方法区归入GC管理范围,这样就没必要单独管理方法区的内存,因此就有了”永久代“这么一说。方法区和操做系统进程的正文段(Text Segment)的做用很是相似,它存储的是已被虚拟机加载的类信息、常量(从JDK7开始已经移至堆内存中)、静态变量等数据。现设置JVM参数为”-XX:MaxPermSize=20M”(方法区最大内存为20M)。
1 package com.jvm; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * -XX:MaxPermSize=20M 方法区最大大小20M 8 * Created by yulinfeng on 7/11/17. 9 */ 10 public class Test { 11 12 public static void main(String[] args) { 13 List<String> list = new ArrayList<String>(); 14 int i = 0; 15 while (true) { 16 list.add(String.valueOf(i++).intern()); //不断建立线程 17 } 18 } 19 }
实际上对于以上代码,在JDK6、JDK7、JDK8运行结果均不同。缘由就在于字符串常量池在JDK6的时候仍是存放在方法区(永久代)因此它会抛出OutOfMemoryError:Permanent Space;而JDK7后则将字符串常量池移到了Java堆中,上面的代码不会抛出OOM,若将堆内存改成20M则会抛出OutOfMemoryError:Java heap space;至于JDK8则是纯粹取消了方法区这个概念,取而代之的是”元空间(Metaspace)“,因此在JDK8中虚拟机参数”-XX:MaxPermSize”也就没有了任何意义,取代它的是”-XX:MetaspaceSize“和”-XX:MaxMetaspaceSize”等。