JVM入门——运行时数据区

jdk1.7.0_79 

  这张图我相信基本上对JVM有点接触的都应该很熟悉,可以说这是JVM入门的第一课。其中的“堆”和“虚拟机栈(栈)”更是耳熟能详。下面将围绕这张图对JVM的运行时数据区做一个简单介绍。

程序计数器(Program Counter Register)

  这和计算机操作系统中的程序计数器类似,在计算机操作系统中程序计数器表示这个进程要执行的下个指令的地址,对于JVM中的程序计数器可以看做是当前线程所执行的字节码的行号指示器,每个线程都有一个程序计数器(这很好理解,每个线程都有在执行任务,如果线程切换后要能保证能恢复到正确的位置),重要的一点——程序计数器,这是JVM规范中唯一一个没有规定会导致OutOfMemory(内存泄露,下文简称OOM)的区域。换句话上图中的其余4个区域,都有可能导致OOM。

☆虚拟机栈(Java Virtual Machine Stacks)

  这块内存区域就是我们常常说的“栈”,我们所熟知的是它用于存放变量,也就是说例如:

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所允许的深度。

  对于单线程情况下,无论如何抛出的都是StackOverflowError。如果要抛出OOM异常,导致的原因是不断地在创建线程,直到将内存消耗殆尽。

  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”等。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器学习算法与理论

从python2到python3

从Python2到Python3需要进行一定的转换,我们知道python版本更新后部分数据结构和语法有了少许的差别,这里简单列出几个,后续会进行更新: 1、pr...

36180
来自专栏JackeyGao的博客

Python 高级并发3

本篇主要讲案例, 两个使用Concurrent.futures实现的并发, 一个是多线程, 一个是多进程。

14420
来自专栏纯洁的微笑

jvm系列(二):JVM内存结构

所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实...

43740
来自专栏纯洁的微笑

Java8内存模型—永久代(PermGen)和元空间(Metaspace)

根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

25620
来自专栏zhisheng

Java研发方向如何准备BAT技术面试答案(上)

1. 面向对象和面向过程的区别 面向过程 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Un...

46140
来自专栏编程心路

Java虚拟机内存管理(一)—内存划分

Java 虚拟机作为运行 Java 程序抽象出来的计算机,具有内存管理的能力,像内存分配、垃圾回收等这些相关的内存管理问题,Java 虚拟机都会帮我们解决,所以...

17950
来自专栏武培轩的专栏

JVM内存模型

运行时数据区域 Java虚拟机(Java Virtual Machine,简称JVM)在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这...

41040
来自专栏Android机动车

走进JVM

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动...

11520
来自专栏IMWeb前端团队

ECMAScript 2015 (ES6) in Node.js(译)

本文作者:IMWeb link 原文出处:IMWeb社区 未经同意,禁止转载 原文:https://nodejs.org/en/docs/es6/ ...

199100
来自专栏技术碎碎念

探究JVM——运行时数据区

最近在读《深入理解Java虚拟机》,收获颇丰,记录一下,部分内容摘自原书。 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域...

35570

扫码关注云+社区

领取腾讯云代金券