jvm内存区域

目录

一、program counter

二、direct memory

三、native method stacks

四、method area

五、jvm stacks

六、heap


        

        jvm内存区域包含:program counter,JVM stacks,method area,native method stacks,direct memory,heap。

一、program counter

        程序计数器,该内存区域存放的是需要执行的指令位置,属于线程私有,jvm的运行过程大致可以类似于

        while(not end){

                从pc中找到对应的指令位置信息,定位到响应指令,执行指令;

                program counter++;

        }

二、direct memory

        直接内存,是在java1.4以后出现的内存区域,jvm属于用户空间,在没有direct memory之前,如果用户空间要去访问内核空间的数据,需要将内核态的指定数据复制到jvm区域,然后才能使用,但是有了direct memory之后,就无须复制,可以直接只用内核态的数据。这部分数据jvm无法管理,由操作系统管理。

三、native method stacks

        本地方法栈,属于线程私有,里面存储的是一些本地方法,例如调用一些c++相关的方法

四、method area

        方法区,所有线程锁共享,用于存储加载的类信息。需要注意的一点是method area属于一种逻辑概念,而我们经常提到的perm space(永久代)和meta space(元空间)是对method area的具体实现,在1.8之前使用的perm space,1.8之后使用的是meta space,这里的虚拟机指的hotspot。这两个实现存在一些差别:

        1)perm space:字符串常量池存储于perm space,但是它有一个很大的问题,无法触发FGC,也就是无法垃圾回收。它使用jvm内存空间,在jvm虚拟机启动之前需要指定其大小,并且无法动态扩容,可能会出现的方法区被占满的情况,尤其是项目出现大量动态类,例如lambda表达式。

        2)meta space:字符串常量池存在于heap(堆),可以触发FGC,使用的是物理存储空间。

        method area包含一个run-time constant pool(运行时常量池),里面存储的信息就是在前文介绍class文件结构的时候提到的contstan-pool的相关数据。

五、jvm stacks

        虚拟机栈,每一个线程都有一个jvm stacks,每个jvm stacks由多个frame(栈帧)组成。

        每个线程中的每个方法在虚拟机中就是一个个栈帧,栈帧由:

        1)lacal variables,局部变量表,存储局部变量数据信息,包含方法的入参以及方法体中使用到的局部变量,对于普通方法和动态方法这里有一个区别,如果是静态方法,局部变量表中的第一个位置存储的是入参,但是如果是普通方法,局部变量表中的第一个位置为this,代表当前对象;

        2)operand stacks,操作数栈,用来临时存放要被操作的局部变量;

        3)dynamic linking,动态链接,指向常量池中的相关地址,例如在A方法中调用了B方法,那么在A方法的栈帧中就有一个指向常量池中B方法的动态链接;

        4)return address,返回地址,指的是方法执行完毕以后返回的地址,还是以A方法中调用了B方法为例,在B方法的栈帧中的return address就是B方法的返回值放回A方法的位置。

        在这里举一个例子更清晰的说一下局部变量表和操作数栈的概念,并且对比一下i++和++i的区别:

public static void main(String[] args) {
        int i = 0;
        i = i++;
    }

        先通过插件jclasslib查看一下它的局部变量表:

 然后看一下它的操作过程:

        

        这段代码的操作指令为:1)将10放入操作数栈;2)将10从操作数栈弹出,存入局部变量表索引为1的位置;3)将局部变量表中索引位置为1的数据存入操作数栈中;4)局部变量表索引位置为1的数据加1;4)将操作数栈中栈顶数据(10)弹出,存入存入局部变量表索引为1的位置。

public static void main(String[] args) {
        int i = 10;
        i = ++i;
    }

上面这段代码片段和第一个代码片段的局部变量表是一样的,但是指令执行顺序不一致,如下

         看这段指令集,它和第一段代码的指令集的区别在于,他是现在局部变量表中执行了加1操作,然后在将值放入操作数栈中。

        因此上面这两段代码中最后i的值也就出来了,i=i++,值仍为10,i=++i,值为11。

下面在分析一段代码,顺便介绍几个常用指令以及new对象的一个过程:

public static void main(String[] args) {
        JvmStack jvmStack = new JvmStack();
        int result = jvmStack.add(10);
    }

    public int add(int param){
        return param + 1;
    }

1)执行new指令,将对应指针放入操作数栈,并赋初始值,如果对应类还没加载,需要先进行类的加载;

2)将操作数栈中指针复制一份,并且放入操作数栈中,与执行复制的指针指向同一个对象;

3)弹出操作数栈中最上面的指针,并查找到相关对象,赋初始值

4)弹出操作数栈中的指针,并将值存储到局部变量中索引为1的位置(到这个位置完成对象新建过程,在DCL模式中之所以加volatile关键字的原因就是234可能发生指令重排,导致读取到还没赋初始值的对象)

5)将常量10放入操作数栈;6)执行add普通方法;7)将操作数栈中的数据弹出,赋值到局部变量表中索引为2的位置;8)结束 

介绍一下invoke相关指令:

        1.invokeStatic:调用静态方法

        2.invokespecial :调用private和构造方法

        3.invokeVirtual:调用普通方法

        4.invokeInterface:调用接口方法

        5.invokeDynamic:调用动态产生的方法,lambda表达式/反射/动态语言,动态产生的class产生的指令

对上面指令做一个简单介绍:

        bipush:The immediate byte is sign-extended to an int value. That value is pushed onto the operand stack(摘自于官方文档),意思就是将一个byte数据转换成int数据存放于操作数栈。

        istore_1:The <n> must be an index into the local variable array of the current frame (§2.6). The value on the top of the operand stack must be of type int. It is popped from the operand stack, and the value of the local variable at <n> is set to value(摘自于官方文档),意思是位置1必须在局部变量表中存在,要存储的数据必须为int类型,将数据从操作数栈中弹出,然后将该数据存储到局部变量表。

        iload_1:The <n> must be an index into the local variable array of the current frame (§2.6). The local variable at <n> must contain an int. The value of the local variable at <n> is pushed onto the operand stack(摘自于官方文档),1必须是局部变量表中的索引,局部变量中1位置必须包含int类型,将局部变量中1位置的数据存放入操作数栈。

        iinc 1 by 1:The index is an unsigned byte that must be an index into the local variable array of the current frame (§2.6). The const is an immediate signed byte. The local variable at index must contain an int. The value const is first sign-extended to an int, and then the local variable at index is incremented by that amount(摘自于官方文档),在局部变量表中,将索引1位置的数据加1。

六、heap

        堆,jvm中大部分数据存储的位置,也是垃圾回收最需要关注的地方,在1.8之后字符串常量池是位于堆中。

        目前堆针对垃圾回收分为新生代和老年带,他们的比例是1:2(可以通过java -XX:+PrintFlagsFinal -version查看相关信息),其中新生代又分为eden区和两个survivor区。需要注意的是zgc以后已经没有分代概念;g1是逻辑分区,物理不分区;其它既是逻辑分区又是物理分区。也就是说在g1之前堆是按照上面这种方式进行物理分区。

        那么对象是如何分配的呢?如下图

         对象先尝试在栈上分配,如果能够栈上分配,则随着方法执行结束,对应栈帧被弹出,然后对象被回收;如果无法栈上分配,则判断对象大小,如果对象过大,则直接进入老年带,然后等着FGC回收;如果为小对象,则尝试tlab分配,如果能够分配,则进入tlab区(tlab位于eden区);如果无法在tlab分配,则进入公共eden区。eden区的对象通过YGC,如果发现当前对象不再被使用,则被回收;如果还有使用则进入survivor1区,需要注意的是这里的survivor1和survivor2为逻辑上的区分,再一次YGC以后,survivor1区和eden区的存活对象都先判断是否达到晋升老年带的年龄,如果达到则进入老年带,等待FGC;剩下的所有信息进入survivor2区,然后一直重复上述流程。

栈上能分配的信息:

        1)线程私有小对象

        2)无逃逸对象,只在当前线程某段代码使用,其它地方无使用

        3)标量替换对象,指的是某个对象只有基本数据类型组成,例如对象T只有int a;int b;那么T可以使用a和b来表示。

        线程本地分配TLAB(Thread local allocation buffer),TLAB占用eden,默认大小为1%,防止多线程竞争使用eden区,提高响应效率,用于存储一些小对象。

        

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值