如何理解java虚拟机的基本结构

发布时间:2021-09-27 09:43:22 作者:柒染
来源:亿速云 阅读:106

今天就跟大家聊聊有关如何理解java虚拟机的基本结构,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

1. java 虚拟机的架构

如何理解java虚拟机的基本结构

2. java堆

如何理解java虚拟机的基本结构

根据java回收机制的不同,java堆有可能拥有不同的结构。最为常见的一种构成是将整个java堆分为新生代和老年代。其中新生代存放新生对象或者年龄不大的对象,老年代则存放老年对象。新生代有可能分为eden区、s0区、s1区,s0区和s1区也被称为from和to区,他们是两块大小相同、可以互换角色的内存空间。

3. java栈

java栈是一块线程私有的内存空间。如果说,java堆和程序数据密切相关,那么java栈就是和线程执行密切相关。线程执行的基本行为是函数调用,每次函数调用的数据都是通过java栈传递的。

在java栈中保存的主要内容为栈帧。每一次函数调用,都会有一个对应的栈帧被压入java栈,每一个函数调用结束,都会有一个栈帧被弹出java栈。如下图:

如何理解java虚拟机的基本结构

函数1对应栈帧1,函数2对应栈帧2,依次类推。当前正在执行的函数所对应的帧就是当前帧(位于栈顶),它保存着当前函数的局部变量、中间计算结果等数据。

当函数返回时,栈帧从java栈中被弹出,java方法区有两种返回函数的方式,一种是正常的函数返回,使用return指令,另一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出。

java虚拟机提供了参数-Xss来指定线程的最大栈空间,这个参数也直接决定了函数调用的最大深度:

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参数,结果如下:

如何理解java虚拟机的基本结构

如何理解java虚拟机的基本结构

可以看到,在进行大约2900次调用后,发生了栈溢出错误,通过增大-Xss的值,可以获得更深的调用层次,尝试使用参数-Xss512K,可以看到调用次数明显增加:

如何理解java虚拟机的基本结构

在一个栈帧中,至少包含局部变量表、操作数栈和帧数据区几个部分。

1 局部变量表

局部变量表用于保存函数的参数以及局部变量。局部亦是表中的变量只在当前函数调用中有效,当函数调用结束后,函数栈帧销毁,局部变量表也会随之销毁。

由于局部变量表在栈帧之中,因此,如果函数的参数和局部变量较多,会使局部变量表膨胀,从而每一次函数调用就会占用更多的栈空间,最终导致函数的嵌套调用次数减少。

下面这段代码,第一个recursion() 函数有3个参数和10个局部变量,因此,其局部变量表含有13个变量,而第2个recursion()函数不含有任何参数和局部变量。当这两个函数被嵌套调用时,第2个rescursion()函数可以拥有更深的调用层次。

 private static int count = 0;

    public static void recursion(long a, long b, long c) {
        long e = 1, f = 2, g = 3, h = 4, i = 5, k = 6, q = 7, x = 8, y = 9, z = 10;
        recursion(a, b, c);
    }
    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 执行上述代码中的第1个rescursion() 函数,结果如下:

如何理解java虚拟机的基本结构

使用-Xss256k 执行上述代码中的第2个rescursion() 函数,结果如下:

如何理解java虚拟机的基本结构

可以看到,在相同的栈容量下,局部变量少的函数可以支持更深层次的函数调用。

栈桢中的局部变量表中的槽位是可以重用的,如果局部变量的作用域范围超过了其作用域,那么在其作用域之后声明的新的局部变量就很有可能会复用局部变量a的槽位,从而达到节省资源的目的。局部变量表中的变量也是重要的垃圾回收根节点,被局部变量表中直接或间接引用的对象都是不会回收的。

如以下代码:

public void localVarGc1() {
        byte[] a = new byte[6 * 1024 * 1024];
        System.gc();
    }

    public void localVarGc2() {
        byte[] a = new byte[6 * 1024 * 1024];
        a = null;
        System.gc();
    }

    public void localVarGc3() {
        {
            byte[] a = new byte[6 * 1024 * 1024];
        }
        System.gc();
    }

    public void localVarGc4() {
        {
            byte[] a = new byte[6 * 1024 * 1024];
        }
        int c = 10;
        System.gc();
    }

    public void localVarGc5() {
        localVarGc1();
        System.gc();
    }

    public static void main(String[] args) {
        Demo05 d = new Demo05();
        d.localVarGc1();
        //d.localVarGc2();
        //d.localVarGc3();
        //d.localVarGc4();
        //d.localVarGc5();
    }

上述代码中,每一个localVarGcN()函数都分配了一块6MB的堆内存,并使用局部变量引用这块空间。可以使用参数-XX:+PrintGC 分别执行上述函数,在输出的日志中,可以看到垃圾回收前后堆的大小,进而推断byte数组是否被回收。

[GC (System.gc())  8765K->6664K(251392K), 0.0041586 secs]
[Full GC (System.gc())  6664K->6515K(251392K), 0.0039022 secs]
[GC (System.gc())  8765K->568K(251392K), 0.0012696 secs]
[Full GC (System.gc())  568K->395K(251392K), 0.0039405 secs]
[GC (System.gc())  8765K->6696K(251392K), 0.0039619 secs]
[Full GC (System.gc())  6696K->6515K(251392K), 0.0039020 secs]
[GC (System.gc())  8765K->536K(251392K), 0.0010555 secs]
[Full GC (System.gc())  536K->370K(251392K), 0.0033685 secs]
[GC (System.gc())  8765K->6744K(251392K), 0.0034826 secs]
[Full GC (System.gc())  6744K->6539K(251392K), 0.0045563 secs]
[GC (System.gc())  6539K->6539K(251392K), 0.0007713 secs]
[Full GC (System.gc())  6539K->395K(251392K), 0.0032212 secs]
2. 操作数栈

操作数栈主要用于保存计算过程的中间结果,同事作为计算过程中变量临时的存储空间。操作数栈也是一个先进后出的数据结构,只支持入栈和出栈两种操作。

3. 帧数据区

帧数据区时候为了支持常量池解析、正常方法返回和异常处理等。大部分Java字节码指令需要进行常量池访问,在帧数据区中保存着访问常量池的指针,方便程序访问常量池。

提示:由于每次函数调用都会产生对应的栈帧,从而占用一定的栈空间,因此,如果栈空间不足,那么函数调用自然无法继续进行下去。当请求的栈深度大于最大可用栈深度时,系统会抛出StackOverflowError栈溢出错误。 举个例子:

4. 栈上分配

栈上分配是Java虚拟机提供的一项优化技术,它的基本思想是:对于那些线程私有的对象(这里指不可能被其他线程访问的对象),可以将它们打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统的性能。

栈上分配的以及技术基础是进行逃逸分析。逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。

下面这个简单示例显示了对非逃逸对象的栈上分配:

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

public static void alloc() {
    User u = new User();
    u.id = 5;
    u.name = "geym0909";
}

public static void main(String[] args) {
    long b = System.currentTimeMillis();
    for(int i = 0; i < 10_0000_0000; i++) {
        alloc();
    }
    long e = System.currentTimeMillis();
    System.out.println(e - b);
}

上述代码在主函数中进行了1亿次alloc()调用来创建对象,由于User对象实例需要占用约16字节的空间,因此累计分配空间将近1.5GB。如果堆空间小于这个值,就必然会发生GC。使用如下参数运行上述代码:

-server -Xmx10m -Xms10m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:-UseTLAB -XX:+EliminateAllocations

程序执行后,结果如下:

如何理解java虚拟机的基本结构

注:在本人机器上,使用如下参数(即不指定任何栈上分配相关的参数),结果依然无大量gc日志:

-server -Xmx10m -Xms10m -XX:+PrintGC

如何理解java虚拟机的基本结构

再关闭逃逸分析,则结果如下:

-server -Xmx10m -Xms10m -XX:+PrintGC -XX:-DoEscapeAnalysis

如何理解java虚拟机的基本结构

可见,在本人机器上逃逸分析、栈上分配是默认开启的。

对于大量的零散小对象,栈上分配提供了一种很好的对象分配优化策略,栈上分配速度快,并且可以有效避免垃圾回收带来的负面影响,但由于和堆空间相比,栈空间较小,因此,大对象无法也不适用在栈上分配。

5. 方法区

和堆一样,方法区是一块所有线程共享的内存区域,它用于保存系统的类信息,比如类的字段、方法、常量池等。方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区的溢出,虚拟机同样会抛出内存溢出错误。

在JDK1.6、JDK1.7中,方法区可以理解为永久区(Perm)。永久区可以使用参数 -XX:PermSize-XX:MaxPermSize 指定,默认情况下,-XX:MaxPermSize 为64M。一个大的永久区可以保存更多的类信息。如果系统使用了一些动态代理,那么有可能会在运行时生成大量的类,如果这样,就需要设置一个合理的永久区大小,确保不发生永久区内存溢出。

在JDK1.8中,永久区已经被彻底移除,取而代之的是元数据区,元数据区大小可以使用参数 -XX:MaxMetaspaceSize 指定(一个大的元数据区可以使系统支持更多的类),这是一块堆外的直接内存。与永久区不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。

如果元数据区发生异常,虚拟机一样会抛出异常。

4. java虚拟机参数总结


看完上述内容,你们对如何理解java虚拟机的基本结构有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。

推荐阅读:
  1. Java虚拟机体系结构
  2. 深入理解Java虚拟机体系结构

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:Java中的jdk命令行工具有哪些

下一篇:如何使用java实现操作系统中的最佳置换Optimal算法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》