JVM运行时数据区域及异常实战

声明: 《深入理解Java虚拟机 JVM高级特性与最佳实践 第2版》。以下内容来自书中第二章。

1. JVM概述

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行             ——百度百科

可以将JVM理解成一台机器,这个机器可以用来执行java程序。这样java代码就可以实现一次编写,到处运行了。在不同的机器上配置相应的JVM即可。 在机器上,JVM实际上也是一个程序。

2. JVM内存分配

2.1 运行时数据区域

根据《Java 虚拟机规范(Java SE 7 版)》的规定,Java虚拟机所有管理的内存将会包括以下几个运行时数据区域,如下图所示:

javaRunTimeDataArea.png

简单说一下各个数据区域的作用:

线程私有的数据区域:线程私有区域都是与线程同生共死的,即生命周期与线程相同。
线程共享的数据区域:生命周期与JVM相同

程序计数器:每个线程都有的"小本本",线程不是连续不断工作的。再次运行时它就要看看“小本本”里的内容才知道自己该从什么地方继续做下去。
在JVM中的多线程是通过轮流切换的方式执行的,在任何时刻一个CPU(相当于多核CPU的一个核)只能运行一个线程。如果某个进程的某个线程占用CPU很长的时间,那么其他的线程会一直等下去吗?为了让用户不会有:”哇!我这个傻X计算机怎么这么卡啊!“的错觉。CPU就给了每个线程分配了一个CPU时间,当线程的CPU时间用完之后,他就会把CPU的计算资源让出来给其他线程,等到下次轮到它的时候它再执行。所以每个线程都需要一个私有的程序计数器来记录自己执行到哪儿了。它实际可以理解成一个记录程序执行的字节码的行号的指示器。 计数器只会占用内存中很小的一部分空间。

Java虚拟机栈:程序员口中常说的“堆栈”大抵说的就是这个区域中的局部变量表。虚拟机栈是用来描述Java方法执行时的内存模型。这种描述是通过存储方法开始执行(入栈)到方法结束(出栈)过程中的局部变量表、操作数栈、动态链接及方法的出口等信息来是实现的。方法在执行时会创建一个栈帧,用来存储前面说的各种信息,方法完成,栈帧出栈。
局部变量表存放了编译期间可知的各种基本数据类型和引用类型,以前上课的时候说的堆栈的时候,老师可能会画这样的一张图:

Variable Value
variable1 13
variable2 2.3
variable3 'a'
variable4 true
zhangsan(Student的一个实例) 指向堆中张三实例的地址空间

说的就是这个局部变量表。局部变量表所需要的内存空间在编译期间就分配完成。
以上可以对栈的作用做个小小的结论:

  1. 只有在方法调用时,才为当前栈分配一个帧,然后将该帧压入栈。
  2. 帧中存放了方法的局部变量表,当方法执行完之后,对应的帧则从栈中弹出。

JVM规范中,对这个数据区域规定了两种异常状况:

  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
  • OutOfMemoryError:虚拟机栈动态扩展时无法申请都足够的内存空间

本地方法栈:与虚拟机栈相似,不过虚拟机栈是为JVM执行java方法,而本地方法栈则是为虚拟机使用到的Native方法服务。
本地方法栈也可能会出现StackOverflowError和OutOfMemoryError。

Java堆:对大多数应用来说,这个区域是JVM所管理的最大的内存空间,也是GC重点关注的区域。该区域被线程共享,存在的目的就是为了存放对象实例,几乎所有的对象实例和数组都要在堆上分配。如果Java堆中没有空间可以用来实例化对象,而且也没法再申请新的内存时,该区域会抛出OutOfMemoryError。

方法区:线程共享区域,用于存储已经被虚拟机加载的类信息、常量、静态变量、即使编译器后的代码等数据。这个区域也有人称其为永久代。方法区无法满足内存分配需求时也会抛出OutOfMemoryError。 运行时常量池也是方法区中的一部分,class文件会包括类的版本,字段,方法,接口等描述信息,也会有个常量池,用于存放编译期生成的各种字面量和符号引用。这部分内容在类加载之后进入到方法区的运行时常量池中存放。该区域具有动态性,即常量不一定是编译期间就确定的,在运行期间也可以有新的常量产生,进入运行时常量池中。

3. 异常代码实战

实战一下数据提供的代码,旨在对运行时数据区域内存分配和使用有更深的理解,当出现相关异常的时候能够快速地定位到异常区域和异常代码。

首先了解一下IDEA如何配置JVM启动时的参数。Run—>EditConfigurations

JVMConfiguration.png

JVM相关配置参数说明可以通过在CMD中通过java -X查看

JVMargs.png

Java 堆溢出

Java堆是用来存储对象实例和数组的,只要的不停的创建对象,且确保对象不会被回收,当对象的数量达到堆最大的容量限制后,就会产生内存溢出异常了。下面的JVM参数设置了Java堆内存的大小为20MB,不可扩展(将最大值-Xms和最小值-Xmx设为一样即可避免堆自动扩展)。通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常的时候Dump出当前的内存堆转储快照,以便我们后面的分析。

JVM参数配置

-Xms20m 
-Xmx20m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=E:\\heapdump

代码如下:

public class HeapOOM {
    static class OOMObject{}
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
    //不停地创建对象,直到OOM
        while(true){
            list.add(new OOMObject());  
        }
    }
}

程序运行结果如下:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to E:\\heapdump\java_pid8948.hprof ...
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 per.ling.JVMPractice.HeapOOM.main(HeapOOM.java:13)
Heap dump file created [28866091 bytes in 0.206 secs]

使用JDK自带的内存映像分析工具jvisualVM来分析程序dump出来的堆转储快照。装入文件后点击类,我们可以看到类名和它对应的实例个数及占用的内存空间。

OOMprof.png

打开该文件可以看到OOMObject这个类有810326个实例,占用内存13M左右。这里我们可以看到类实例相关的情况,查看概要我们还可以看到相关的线程及可能出现异常的代码块。

OOMprof3.png

如果是内存泄露的话,我们可能还需要观察一下相关的GC Roots。
Java的内存溢出有很多中情况,刚兴趣可以搜一下,学习一波。

虚拟机栈溢出

虚拟机栈可能抛出的异常:

  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
  • OutOfMemoryError:虚拟机堆栈动态扩展时无法申请都足够的内存空间

JVM参数如下

-Xss128k
-XX:+HeapDumpOnStackOverflowErrow
-XX:HeapDumpPath=E:\\heapdump

代码如下

public class StackOOM {

  private static long stackLength = 0L;

  public static void main(String[] args) {
    try {
      stackLeak();
    } catch (Throwable e){
      System.out.println("The length of statck is " + stackLength);
      throw e;
    }
  }

  private static void stackLeak() {
    stackLength++;
    stackLeak();
  }
}

控制台输出如下:

The length of statck is 41351
Exception in thread "main" java.lang.StackOverflowError
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)

小结:
StackOverflowerError主要是针对运行时的栈而言,而OutOfMemoryError(内存溢出)针对的是整个内存区域。后者出现的原因主要是申请内存时没有更多的内存空间导致的,而导致这样的原因有很多。
详情可以参见: Java常见的几种内存溢出及解决方案

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350