JVM运行时数据区

栏目: Java · 发布时间: 7年前

内容简介:根据 JVM 规范,JVM 内存共分为内存空间(Runtime Data Area)中可以按照是否线程共享分为两块,线程共享的是方法区(Method Area)和堆(Heap),线程独享的是虚拟机栈(VM Stack),本地方法栈(Native Method Stack)和PC寄存器(Program Counter Register)。具体参见下图:

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

内存空间(Runtime Data Area)中可以按照是否线程共享分为两块,线程共享的是方法区(Method Area)和堆(Heap),线程独享的是虚拟机栈(VM Stack),本地方法栈(Native Method Stack)和PC寄存器(Program Counter Register)。

具体参见下图:

JVM运行时数据区
区域 是否线程共享 是否会内存溢出
程序计数器 不会
虚拟机栈
本地方法栈
方法区
  • 虚拟机栈(线程私有)

    每个线程有一个私有的栈,随着线程的创建而创建。栈里面存放着一种叫做“栈帧”的东西,每个方法在执行的时候会创建一个栈帧,存储了局部变量表(基本数据类型和对象引用),操作数栈,动态连接,方法出口等信息。 每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。 通常所说的栈,一般是指虚拟机栈中的局部变量表部分。局部变量表所需的内存在编译期间完成分配。 栈的大小可以固定也可以动态扩展,当扩展到无法申请足够的内存,则OutOfMemoryError。 当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误,不过这个深度范围不是一个恒定的值,我们通过下面这段程序可以测试一下这个结果:

    // 栈溢出测试源码
    package com.paddx.test.memory;
    /**
     * Created by root on 2/28/17.
     */
    public class StackErrorMock {
        private static int index = 1;
    
        public void call() {
            index++;
            call();
        }
    
        public static void main(String[] args) {
            StackErrorMock mock = new StackErrorMock();
            try {
                mock.call();
            } catch(Throwable e) {
                System.out.println("Stack deep: " + index);
                e.printStackTrace();
            }
        }
    }
    复制代码

    运行三次,可以看出每次栈的深度都是不一样的,输出结果如下:

    JVM运行时数据区
    JVM运行时数据区
    JVM运行时数据区

    查看三张结果图,可以看出每次的Stack deep值都有所不同。究其原因,就需要深入到JVM的源码中才能探讨,这里不作赘述。 虚拟机栈除了上述错误外,还有另一种错误,那就是当申请不到空间时,会抛出OutOfMemoryError。这里有一个小细节需要注意,catch捕获的是Throwable,而不是Exception,这是因为StackOverflowError和OutOfMemoryError都不属于Exception的子类。

  • 本地方法栈(线程私有)

    和虚拟机栈类似,主要为虚拟机使用到的Native方法服务。 也会抛出StackOverflowError和OutOfMemoryError。

  • PC寄存器(线程私有)

    PC寄存器,也叫程序计数器。JVM支持多个线程同时运行,每个线程都有自己的程序计数器。倘若当前执行的是JVM方法,则该寄存器中保存当前执行指令的地址;倘若执行的是native方法,则PC寄存器为空。 这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError情况的区域。

  • 堆(线程共享)

    堆内存是JVM所有线程共享的部分,在虚拟机启动的时候就已经创建。 和程序开发密切相关,应用系统对象都保存在 Java 堆中。所有的对象和数组都在堆上进行分配。这部分空间可通过GC进行回收。 对分代GC来说,堆也是分代的,是GC的主要工作区间。 当申请不到空间时,会抛出OutOfMemoryError。下面我们简单的模拟一个堆内存溢出的情况:

    package com.paddx.test.memory;
    import java.util.ArrayList;
    import java.util.List;
    /**
     * Created by root on 2/28/17.
     */
    public class HeapOomMock {
        public static void main(String[] args) {
            List<byte[]> list = new ArrayList<byte[]>();
            int i = 0;
            boolean flag = true;
            while(flag) {
                try {
                    i++;
                    list.add(new byte[1024 * 1024]); // 每次增加1M大小的数组对象
                }catch(Throwable e) {
                    e.printStackTrace();
                    flag = false;
                    System.out.println("Count = " + i); // 记录运行的次数
                }
            }
        }
    }
    复制代码

    首先配置运行时虚拟机的启动参数:

    JVM运行时数据区
    然后运行代码,输出结果如下:
    JVM运行时数据区

    注意,这里我们指定了堆内存的大小为16M,所以这个地方显示的Count=13(这个数字不是固定的),至于为什么会是13或其他数字,需要根据GC日志来判断。

  • 方法区(线程共享)

    方法区也是所有线程共享的。主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。

    这个区域的内存回收目标主要针对常量池的回收和对类型的卸载。

    当方法区无法满足内存分配需求时,则抛出OutOfMemoryError异常。

    在HotSpot虚拟机中,用永久代来实现方法区,将GC分代收集扩展至方法区,但是这样容易遇到内存溢出的问题。

    JDK1.7中,已经把放在永久代的字符串常量池移到堆中。

    JDK1.8撤销永久代,引入元空间。

附件(栈、堆、方法区交互):

JVM运行时数据区

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Concepts, Techniques, and Models of Computer Programming

Concepts, Techniques, and Models of Computer Programming

Peter Van Roy、Seif Haridi / The MIT Press / 2004-2-20 / USD 78.00

This innovative text presents computer programming as a unified discipline in a way that is both practical and scientifically sound. The book focuses on techniques of lasting value and explains them p......一起来看看 《Concepts, Techniques, and Models of Computer Programming》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具