3.2 栈帧组成之操作数栈

操作数栈是栈帧的主要内容之一,它主要用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间。

操作数栈也是一个先进后出的数据结构,只支持入栈和出栈两种操作,许多java字节码指令都需要通过操作数栈进行参数传递。比如add指令,它就会在操作数栈中弹出两个整数并进行加法计算,计算结果会被入栈,如图:显示了iadd前后操作数栈的变化。

3.3 帧数据区

除了局部变量表和操作数栈,java栈帧还需要一些数据来支持常量池的解析、正常方法返回和异常处理等。大部分java字节码指令需要进行常量池访问,在帧数据区中保留着访问常量池的指针,方便程序访问常量池。

此外,当函数返回或者出现异常时,虚拟机必须恢复调用者函数的栈帧,并让调用者函数继续执行下去。对于异常处理,虚拟机必须有一个异常处理表,方便在发生异常时找到处理异常的代码,因此异常处理表也是帧数据区中重要的一部分,一个典型的异常处理表如下所示:

Exception table:

from   to    target   type

4        16      19      any

19       21      19      any

它表示在字节码偏移量4--16字节可能抛出任意异常,如果抛出异常,则跳转到字节码偏移量19处执行。当方法抛出异常时,虚拟机就会查找类似的异常表来处理,如果无法在异常表中找到合适的处理方法,则会结束当前函数调用,返回调用函数,并在调用函数中抛出相同的异常,并查找调用函数的异常表来进行处理。

3.4 栈上分配

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

栈上分配的一个技术基础是进行逃逸分析,逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。如下代码所示显示了一个逃逸对象:

private static User u;

public static void alloc(){

  u = new User();

  u.id = 5;

  u.name = "jim";

}

对象u是类的成员变量,该字段有可能被任何线程访问,因此属于逃逸对象,而以下对象显示了一个非逃逸对象:

public static void alloc(){

  User u = new User();

  u.id =  5;

  u.name = "jim";

}

在上述代码中,对象User u 以局部变量的形式存在,并且该对象并没有被alloc()函数返回或者出现任何形式的公开,因此它未发生逃逸,所以对于这种情况,虚拟机就有可能将User u 分配在栈上,而不是在堆上。

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

实例1:测试非逃逸对象的分配空间位置

package com.jvm;

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

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

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

使用-Xmx10M -XX:+PrintGC 虚拟机参数运行代码:

[GC (Allocation Failure) 2048K->544K(9728K), 0.0015011 secs]
10

上述代码在主函数中进行了1亿次alloc()调用进行对象的创建,由于User对象实例需要占用约16byte的空间,因此累计分配空间将达到1.5G,如果堆空间小于这个值,就必然发生GC。而此时我们只分配了最大的堆内存为10M,如果这些对象在堆上创建,必然会引起大量的垃圾回收现象,查看垃圾回收日志,并没有。所以,说明其对象分配在栈上。

实例2:对比测试逃逸对象的分配空间位置:

package com.jvm;

public class OnStackTest {
  public static class User{
    public int id = 0;
    public String name = "";
  }
  public static User u;
  public static void alloc(){
    u = new User();
    u.id = 5;
    u.name = "jim";
  }

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

同样使用虚拟机参数-Xmx10M -XX:+PrintGC设置最大堆空间和打印垃圾回收日志,运行此代码:

可见,发生大量的垃圾回收现象,说明此时堆内存远远不够,需要不断的进行垃圾回收。

4 方法区

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

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

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

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

JVM的基本结构及其各部分详解(二)的更多相关文章

  1. JVM的基本结构及其各部分详解(一)

    1 java虚拟机的基本结构如图: 1)类加载子系统负责从文件系统或者网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间.除了类的信息外,方法区中可能还会存放运行时常量池信息,包括字 ...

  2. jvm的基本结构以及各部分详解(转)

    原文链接:https://www.cnblogs.com/zwbg/p/6194470.html 1.java虚拟机的基本结构 图: 1.类加载器子系统从文件系统或者网络中加载Class信息,类信息( ...

  3. java面试题之----JVM架构和GC垃圾回收机制详解

    JVM架构和GC垃圾回收机制详解 jvm,jre,jdk三者之间的关系 JRE (Java Run Environment):JRE包含了java底层的类库,该类库是由c/c++编写实现的 JDK ( ...

  4. PopUpWindow使用详解(二)——进阶及答疑

      相关文章:1.<PopUpWindow使用详解(一)——基本使用>2.<PopUpWindow使用详解(二)——进阶及答疑> 上篇为大家基本讲述了有关PopupWindow ...

  5. [转]文件IO详解(二)---文件描述符(fd)和inode号的关系

    原文:https://www.cnblogs.com/frank-yxs/p/5925563.html 文件IO详解(二)---文件描述符(fd)和inode号的关系 ---------------- ...

  6. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  7. .NET DLL 保护措施详解(二)关于性能的测试

    先说结果: 加了缓存的结果与C#原生代码差异不大了 我对三种方式进行了测试: 第一种,每次调用均动态编译 第二种,缓存编译好的对象 第三种,直接调用原生C#代码 .net dll保护系列 ------ ...

  8. Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

    [Android布局学习系列]   1.Android 布局学习之——Layout(布局)详解一   2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)   3.And ...

  9. logback -- 配置详解 -- 二 -- <appender>

    附: logback.xml实例 logback -- 配置详解 -- 一 -- <configuration>及子节点 logback -- 配置详解 -- 二 -- <appen ...

随机推荐

  1. 大数据新手之路一:安装JDK

    Ubuntu16.04 1.下载jdk-8u192-linux-x64.tar.gz https://www.oracle.com/technetwork/java/javase/downloads/ ...

  2. POJ-3233 Matrix Power Series 矩阵A^1+A^2+A^3...求和转化

    S(k)=A^1+A^2...+A^k. 保利求解就超时了,我们考虑一下当k为偶数的情况,A^1+A^2+A^3+A^4...+A^k,取其中前一半A^1+A^2...A^k/2,后一半提取公共矩阵A ...

  3. 填写数独 洛谷P1784

    题目链接:https://www.luogu.org/problemnew/show/P1784 因为要求行列以及每9个数字组成的中格子都得有1-9这9个数,我们不妨建三个二维数组 第一维代表是第几个 ...

  4. linux grep find查找文件夹、代码中的某行/字符串

    本文转载于:https://blog.csdn.net/Mr_Cat123/article/details/80541658 在Linux中,由于文件很多,代码很长,可能我们只知道其中的一两个字符串, ...

  5. hdu多校第3场C. Dynamic Graph Matching

    Problem C. Dynamic Graph Matching Time Limit: / MS (Java/Others) Memory Limit: / K (Java/Others) Tot ...

  6. vue中父组件给子组件传值,子组件给父组件传值

    1.父组件传给子组件 父元素中 子元素中(通过props传值) 2.子组件传给父组件 子元素中(this.$emit(传过去的名字,传的参数)) 父元素中 通过changeShow的参数data 把修 ...

  7. easyui datebox时间控件如何只显示年月

    easyui datebox控件,只显示年月,不显示年月日 需要的效果图如下: 具体的js代码: <script> $(function(){ intiMonthBox('costTime ...

  8. Swiper.js使用及API介绍

    Swiper.js详细使用教程http://www.swiper.com.cn/api/start/2014/1218/140.html

  9. java课堂笔记2

  10. Linux常用内核参数

    Linux常用内核参数 TCP状态描述 CLOSED:无连接是活动的或正在进行的 LISTEN:服务器在等待进入呼叫 SYN-RECV:一个连接请求已经到达,等待确认 SYN-SENT:应用已经开始, ...