Java虚拟机基本结构

Java虚拟机是JVM类语言的根基,其中动态内存管理和垃圾收集技术是JVM中最重要的特性。本节主要讲述其中的内存管理相关概念。

一 Java虚拟机的基本结构

Java虚拟机结构.jpg

如图所示为Java虚拟机的基本结构,每个模块介绍如下:

  • 类加载子系统
    类加载子系统负责从文件或网络中加载Class字节码信息,然后存放于方法区。
  • 方法区
    方法区是各个线程共享的内存区域,用于存储虚拟机加载的类变量、常量、静态变量以及即时编译后的代码等数据。
  • Java堆
    在虚拟机启动的时候建立,是Java程序最主要的内存工作区域,几乎所有的对象实例和数组都在Java堆上分配。和方法区一样,是各个线程共享的内存区域。可通过-Xmx和Xms虚拟机参数控制Java堆大小。
  • 垃圾回收系统
    垃圾回收是Java虚拟机的重要组成部分,其中的垃圾回收器可以对Java堆、方法区和直接内存进行回收。同时Java堆是回收器的工作重点。
  • 直接内存
    在Java的NIO库中,允许Java程序使用直接内存,它是Java堆外直接向系统申请的内存区域。通常情况下该区域的内存访问速度优于Java堆。
  • Java栈
    Java栈是线程私有的,它的生命周期和线程相同。它在线程创建的时候被创建。Java栈中保存帧信息,每个方法创建的时候都会创建一个栈帧,用于存储局部变量、方法参数、操作数栈、方法出口灯信息,和方法的调用返回密切相关。
  • 本地方法栈
    和Java栈类似,但其中最大的不同是Java栈用于方法调用,而本地方法栈用于本地方法调用。
  • PC寄存器
    该区域也是每个线层私有的空间。Java虚拟会为每个Java线程创建PC寄存器。当当前执行的方法不是本地方法时,PC寄存器就会指向当前正在被执行的指令。若当前执行的方式是本地方法,则PC寄存器的值为undefined。
  • 执行引擎
    执行引擎是虚拟机最核心的组件之一,它负责执行虚拟机的字节码。

二 Java堆

Java堆是和Java应用程序关系最为密切的内存空间。Java堆内存通过垃圾回收机制,垃圾对象会被自动清理,而不需要显示的释放。Java堆分为新生代和老年代。其中新生代存放新生对象或年龄不大的对象,而老年代则存放老年对象。新生代和老年代结构如下图所示:


Java堆结构.png

在大多数情况下,对象首先在Eden区分配,在一次新生代回收后,若对象还存活着则进入S0或S1,在这之后,每经一次新生代回收,若对象还存活着则它的年龄会加1,达到一定年龄后该对象就被认为是老年对象,从而进入老年代。当然这里只是其中一种方式进入老年代,后续文章会有详细叙述。

三 Java栈

Java栈是一块线程私有内存空间。Java栈用于传递每次函数调用的数据。它是一块后进先出的数据结构,在其中保存的主要内容是栈帧。每一次函数调用都会有对应的栈帧压如Java栈,同时函数结束时栈帧被弹出。


Java栈帧.jpg

上图可用下面的代码表示:

public void function1() {
    public void function2();
}

public void function2() {
    public void function3();
}
 public void function3() {
    ....
}
...

在一个栈帧中至少包含局部变量表、操作数栈和帧数据区几个部分。
但是Java栈空间也不能无限使用下去,它受-Xss参数限制,该参数也决定了函数调用的最大深度。
示例:

public class TestStackDeep {
    private static int count = 0;
    public static void recursion() {
        count++;
        recursion();
    }
    public static void main(String[] args) {
        try {
            recursion();
        } catch (Throwable e) {
            System.out.println("deep of calling = " + count);
            e.printStackTrace();
        }
    }
}

上面的代码计算最大栈深度,设置虚拟机参数-Xss256K,其结果为:

deep of calling = 2374

当设置-Xss512K时,结果为:

deep of calling = 9245

栈溢出则会抛出java.lang.StackOverflowError异常。

1)局部变量表
局部变量表用于保存函数的参数以及局部变量,它同样也随函数的调用而生灭,函数变量表可通过jclasslib工具查看,在Idea中,jclasslib可作为插件方式安装。
示例代码:

public class TestStack {

    public void test1() {
        int m, n, i, j, k;
        System.out.println("hello world");
    }

    public void test2(int param1, int param2) {
        long m, n, i, j, k;
        System.out.println("hello world");
    }


    public static void main(String[] args) {
        TestStack testStack = new TestStack();

    }
}

通过查看jclasslib可看到以下内容:


testStack

在jclasslib中,可看到当前类中的静态池,接口字段以及方法等信息。查看方法的Code部分可看到局部变量表统计信息:


局部变量表信息

从上图看出,test2()最大局部变量表占用大小为13字,因为test2()参数为两个int,加上this字段以及5个long变量正好是13字(int占用一个字,this占用一个字,long占用两个字)。查看局部变量表信息可通过LocalVariableTable:


局部变量表

上图中分别对应了局部变量的作用域范围,所在槽位(index),变量名(name)以及数据类型(Descriptor)
栈帧中局部变量表的槽位是可以复用的,如果一个局部变量过了其作用域,那么在其后申明的新局部变量就有可能复用过期局部变量的槽位,从而达到节省资源的目的。
2)操作数栈
操作数栈主要用于保存计算过程中间结果。也作为计算过程中变量的临时存储空间。
3)帧数据区

4)栈上分配
栈上分配是Java虚拟机提供的一项优化技术,基本思想是对于线程私有对象(即不会被其它线程访问到的对象实例),可以将它们分配在栈上,而不必从堆中分配,这样的好处是该对象在函数调用完毕后可以自行销毁而不必接入垃圾回收器,从而提高系统的整体性能。栈上分配的一个基础技术是逃逸分析,逃逸分析的目的是判断对象作用域是否逃逸出函数体。

在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针。它涉及到指针分析和形状分析。
当一个变量(或对象)在子程序中被分配时,一个指向变量的指针可能逃逸到其它执行线程中,或是返回到调用者子程序。如果使用尾递归优化(通常在函数编程语言中是需要的),对象也可以看作逃逸到被调用的子程序中。如果一种语言支持第一类型的延续性在Scheme和Standard ML of New Jersey中同样如此),部分调用栈也可能发生逃逸。
编译器可以使用逃逸分析的结果作为优化的基础:[1]

  • 将堆分配转化为栈分配。如果某个对象在子程序中被分配,并且指向该对象的指针永远不会逃逸,该对象就可以在分配在栈上,而不是在堆上。在有垃圾收集的语言中,这种优化可以降低垃圾收集器运行的频率。
  • 同步消除。如果发现某个对象只能从一个线程可访问,那么在这个对象上的操作可以不需要同步。
  • 分离对象或标量替换。如果某个对象的访问方式不要求该对象是一个连续的内存结构,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

例如下面的代码便是一个逃逸对象:

private static Bean bean;
public static void alloc() {
    bean = new Bean();
    bean.setParam(23);
....
}

其中bean字段可能被其它线程访问到,故属于逃逸对象。
下面的代码显示了非逃逸对象:

public static void alloc() {
     bean = new Bean();
    bean.setParam(23);
....
}

启用逃逸分析需要设置-server执行程序,JVM参数如下:

-server -Xmx10m -Xms10m  -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
  • -Xmx256m -Mms256m 分别制定了堆最大空间和堆最小空间为10M;
  • -XX:+DoEscapeAnalysis 启用逃逸分析;
  • -XX:+PrintGC 打印GC信息;
  • -XX:-UseTLAB 关闭TLAB;
  • -XX:+EliminateAllocations 开启标量替换,允许将对象打散分配到栈上。
    以上参数都是默认启用的。
    示例:
public class OnStackTest {

    public static class User {
        public int id = 0;
        public String name = "";
    }

    public static void alloc() {
        User user = new User();
        user.id = 10;
        user.name = "vincent";
    }

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            alloc();
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}

上面的代码进行了1000000000次调用,但是产生的GC日志很少:

[GC (Allocation Failure)  2047K->536K(9728K), 0.0008531 secs]
7

但如果关闭了逃逸分析,则会产生大量的GC日志。例如将-XX:+DoEscapeAnalysis 替换成-XX:-DoEscapeAnalysis

四 方法区

方法区也是所有线程共享的内存区域,用于保存系统类信息,例如字段,方法常量池等。该区域大小决定了系统可以保存多少类。但若定义了太多类同样也会导致方法区溢出。
在JDK6和JDK7中,方法区可链接为永久区,通过参数-XX:PermSize和-XX:MaxPermSize指定。但在JDK8中,永久区已经被移除,替代为元数据区,可使用-XX:MaxMetaspaceSize参数指定,若不指定该参数,默认情况下虚拟机会耗尽所有可用系统内存,在VisualVM中可观察永久区:


永久区

元数据区溢出虚拟机会抛出java.lang.OutOfMemoryError: Metaspace异常。

参考

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

推荐阅读更多精彩内容