专栏首页技术那些事JVM系列(一):Java虚拟机内存模型
原创

JVM系列(一):Java虚拟机内存模型

一、前言

Java虚拟机,简称JVM(Java Virtual Machine),是Java语言中最为核心的一个东西,Java程序运行离不开它,因为它的存在,使得Java拥有“一次编译,多次运行”的特点。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。

JVM是Java中最难以理解、而且非常重要的知识点,也常常用来衡量一个人Java基本功是否牢靠,更是在面试中被问及最多、最频繁的知识点之一。本文将从Java虚拟机内存模型开始入手,一步步来了解它。

Java虚拟机内存模型是Java程序运行的基础,为了使Java应用程序正常运行,JVM将其内存数据分为程序计数器、虚拟机栈、本地方法栈、堆和方法区,如下图所示:

JVM内存模型

(在JDK1.8开始,已经去掉了方法区的概念,用元空间(Metaspace)进行了代替.)

程序计数器用于存放下一条运行的指令;虚拟机栈和本地方法栈用于存放函数方法调用堆栈信息;Java堆用于存放Java程序运行时所需的对象等数据;方法区用于存放程序的元数据信息

其中,一部分是线程私有的,而另一部分却是线程共享的。

  • 线程私有:程序计数器、虚拟机栈、本地方法栈
  • 线程共享:堆、方法区

二、程序计数器

程序计数器是一块很小的内存空间,用于存放下一条运行的指令,它是线程私有的,可以认作为当前线程的行号指示器。

由于Java是支持线程的语言,当线程数量超过CPU数量时,线程之间根据时间片轮询抢夺CPU资源。对于单核CPU而言,每一时刻,只能有一个线程在运行,而其他线程必须被切换出去。为此,每一个线程都必须用一个独立的程序计数器,来记录下一条要运行的指令。各个线程之间的计数器互不影响,独立工作。

如果当线程正在执行一个Java方法,则程序计数器记录正在执行的Java字节码地址,如果当前线程正在执行一个Native方法,则程序计数器为空。

三、虚拟机栈(栈)

栈保存的是方法的局部变量、部分结果,并参与方法的调用和返回,即:栈帧数据。

1.栈帧

每个方法被执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接方法、返回地址等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈(方法调用)到出栈(方法返回)的过程。

栈帧结构如下图所示:

栈帧结构

如果方法调用时,方法的参数和局部变量相对较多,那么栈帧中局部变量表就会比较大,栈帧就很很大,因此,单个方法调用所需的栈空间大小也会很大。(在程序开发时,尽量避免这种情况,尤其是递归方法中要避免递归调用的深度)

以下代码片段中,通过逐步设置递归方法调用的深度,将会抛出栈溢出异常(StackOverflowError)。

public class StackTest {
    // 递归次数
    private final int count = 100000;

    /**
     * 递归方法
     * @param num
     */
    public void recursionMethod(int num) {
        num++;
        if (num < count) {
            recursionMethod(num);
        }
    }

    @Test
    public void stackDepthTest() {
        recursionMethod(0);
    }
}

2.栈溢出、内存溢出

Java虚拟机规范中允许栈的大小是动态的或者是固定的,定义了两种异常与栈空间相关:StackOverflowErrorOutOfMemoryError。如果线程在计算过程中,请求的栈深度大于最大可用的栈深度,则会抛出StackOverflowError异常,如果栈能够动态扩展,而在扩展过程中,没有足够的内存空间来支持栈的扩展,则会抛出OutOfMemoryError异常。

其中,可以使用JVM参数-Xss来调整设置栈的大小,从而决定了方法调用可以达到的深度。

针对上述代码StackTest中,在递归次数为100000时,将-Xss参数调整为-Xss512M后,未抛出异常。

-Xss参数调整

3.jclasslib工具

篇外话,但觉得还是有必要提出来,在研究JVM时,总是会去研究一些字节码指令、Class类文件结构、大小等数据,而jclasslib工具恰恰满足这些,有了它更有助于我们对Java、JVM有更深入的了解。

大家可根据自己的喜好,选择安装,有单机软件版、IDE插件可供使用,在此,我选择的是在idea中安装了jclasslib插件,方便使用。此工具将伴随着你在JVM的世界里翱翔,一探JVM究竟。

以上述代码为例进行说明,如下图所示,在idea中通过jclasslib插件查看StackTest.class文件,展开方法recursionMethod后,查看Code属性的Misc页签中,当前方法的最大局部变量表的容量为2。因为在该方法中只有一个int类型的参数,所以共占2字。

jclasslib使用示例

关于jclasslib工具的更多使用技巧,在不断的使用中去摸索吧。

四、本地方法栈

本地方法栈和虚拟机栈的功能很相似,虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用

本地方法并不是用Java实现的,而是使用C实现的。本地方法栈保存的是native方法的信息,当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法。

在Hot Spot虚拟机中,是不区分本地方法栈和虚拟机栈的。因此,本地方法栈一样也会抛出异常StackOverflowError和OutOfMemoryError。

五、堆

堆可以说是Java运行时内存中最为重要的部分,几乎所有的对象和数组都是在堆中分配空间的。堆分为新生代和老年代两部分,新生代用于存放刚刚产生的对象和年轻的对象,如果对象一直没有被收回,生存得足够长,老年对象就会被移入老年代。

新生代又可以进一步细分为edensurvivor space0(s0或者from space)和survivor space1(s1或者to space)。eden称之为伊甸园,即对象的出生地,大部分对象刚刚创建时,通常会存放在这里。s0和s1为survivor空间,直译为幸存者,就是指存放其中的对象至少经历了一次垃圾回收,并得以幸存。如果在幸存区的对象到了指定年龄仍未被回收,则有机会进入老年代。

换言之,堆空间简单分为新生代和老年代,新生代用于存放刚产生的新对象,老年代则存放年长的对象(存放时间较长,经过垃圾回收次数较多的对象)。

堆空间结构如下图所示:

堆空间结构

六、方法区

方法区同堆一样,是所有线程共享的内存区域,为了区分堆,又被称为非堆。主要保存的信息是类的元数据,即类的类型信息、常量池、域信息、方法信息,如static修饰的变量加载类的时候就被加载到方法区中。

类型信息包括类的完整名称、父类的完整名称、类型修饰符(public/protected/private)和类型的直接接口类表;常量池包括这个类方法、域等信息所引用的常量信息;域信息包括域名称、域类型和域修饰符;方法信息包括方法名称、返回类型、方法参数、方法修饰符、方法字节码、操作数栈和方法帧栈的局部变量区大小以及异常表。总之,方法区内保存的信息,大部分都来自于class文件。

在Hot Spot虚拟机中,方法区也成为永久区,是一块独立于Java堆的内存空间。虽然叫做永久区,但是永久区中的对象,同样也是可以被GC回收的。只是对于GC的表现和Java堆空间略不相同。对永久区GC的回收,通常主要从两个方面分析:一是GC对永久区常量池的回收,二是永久区对类元数据的回收。

方法区也成为永久区,主要存放常量和类的定义信息。

(在JDK1.8的HotSpot虚拟机中,已经去掉了方法区的概念,用 Metaspace代替,并且将其移到了本地内存来规划了。)

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【JVM系列】1.Java虚拟机内存模型

    Java虚拟机,简称JVM(Java Virtual Machine),是Java语言中最为核心的一个东西,Java程序运行离不开它,因为它的存在,使得...

    xcbeyond
  • 循环?还是递归?

    --------------------------------------------------------------------------

    xcbeyond
  • JDK1.8新特性(二):为什么要关注JDK1.8

    自1998年JDK1.0(Java1.0)发布以来,Java已经受到了学生、程序员、整个软件行业人员等一大批活跃用户的欢迎。这一语言极富活力,不断被用在大大小小...

    xcbeyond
  • JVM 基本介绍

    Java 的设计理念是 WORA(Write Once Run Anywhere,一次编写到处运行)。编译器将 Java 文件编译为 Java .class 文...

    周三不加班
  • JVM 运行时的内存分配

      首先我们必须要知道的是 Java 是跨平台的。而它之所以跨平台就是因为 JVM 不是跨平台的。JVM 建立了 Java 程序和操作系统之间的桥梁,JVM 是...

    IT可乐
  • 【JVM系列】1.Java虚拟机内存模型

    Java虚拟机,简称JVM(Java Virtual Machine),是Java语言中最为核心的一个东西,Java程序运行离不开它,因为它的存在,使得...

    xcbeyond
  • JVM 运行时数据区详解

      Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同数据区域。

    阿豪聊干货
  • Java虚拟机基础——2JVM运行时数据区

    本篇文章主要讲解JVM运行时数据区,所以我们按照线程是否私有的维度将本篇文章一分为二,分为线程私有数据区和所有线程共有的数据区。而在线程私有的数据区又可以分为程...

    隔壁老李头
  • JVM学习笔记

      java引用类型分为四种:类、接口、数组类和泛型参数。其中泛型参数会在编译过程中被擦除。因此 Java 虚拟机实际上只有前三种。在类、接口和数组类中,数组类...

    良辰美景TT
  • 深入理解JVM的内存区域划分

    JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模...

    沁溪源

扫码关注云+社区

领取腾讯云代金券