浅谈JVM内存模型

分析一下JVM的以下模块:

1.程序计数器

2.java虚拟机栈

3.本地方法栈

4.堆

5.方法区

6.直接内存

1.首先是程序计数器

     java程序的运行过程中java编译器先将java文件编译成字节码文件,然后JVM把每一条要执行的字节码交给解释器,翻译成对应的机器码,然后由解释器执行。JVM解释执行字节码文件就是JVM操作Java解释器进行解释执行字节码文件的过程。这一系列运行过程感兴趣的可以再去看一下词法分析器和语法分析器。

     程序计数器可以看做是当前线程所执行的字节码的行号指示器。(这个行号也可以理解为这一行字节码指令的地址,然后运行时根据计数器去寻址执行相应莫一条字节码指令)。java多线程运行过程每个线程有各自私有的计数器,为了确保线程切换后计数器能回到正确的位置,这里要注意的一点是当执行native方法时计数器是为空的,因为native方法时c执行的没有编译过的,也就没有字节码的地址可寻。而字节码解释器的工作就是通过改变程序计数器的值来选择下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器来完成。由此可见程序计数器类似于起到了一个方向盘的作用一般,控制它可以控制程序的运行方向。

扫描二维码关注公众号,回复: 5348735 查看本文章

2.java虚拟机栈 

     描述的是java 方法执行的内存模型:每个方法被执行的时候 都会创建一个“栈帧”用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。声明周期与线程相同,是线程私有的。局部变量表存放了编译器可知的各种基本数据类型(booleanbytecharshortintfloatlongdouble)、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。

3.本地方法栈

  这个部分其实就和java虚拟机栈差不多,唯一的区别就是这个区域就在虚拟机栈是为java方法服务,而这个是为了在程序计数器中提及的native方法提供服务。

4.堆

       说到堆,顺便提一下它的几个兄弟,在程序运行时的内存分配有三种策略吧,分别有静态的,栈式的,和堆式的。

      静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.(例如我们开发中会提前设定一些静态常量等)

    栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。(这个可以参考一下2)

    静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.

5.方法区

       方法区和堆一样,甚至可以称之为特殊的堆。方法区是一块所有线程共享的内存区域。它是用来保存系统的类信息,比如,类的字段,方法,常量池等。它的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出的错误。而在jdk1.6和jdk1.7方法区可以理解为永久区。jdk1.8已经将方法区取消,替代的是元数据区。jdk1.8的元数据区可以使用参数-XX:MaxMetaspaceSzie设定大小,这是一块堆外的直接内存,与永久区不同,如果不指定大小,默认情况下,虚拟机会耗尽可用系统内存 也就是程序中遇到的元数据区内存溢出java.lang.OutOfMemoryError:Metaspace 异常。

  我们可以看一下下图中当程序的一个main方法运行时栈到堆再到方法区的一个大致过程:

6.直接内存

      直接内存在定义中是这么说的:直接内存不是虚拟机规范中定义的内存区域,也不是虚拟机运行时数据区域的一部分。属于堆外内存,也就是本机内存的一部分。在 JDK 引入 NIO 后,直接内存的使用也越来越普遍。通过 native 方法可以分配堆外内存,通过 DirectByteBuffer 对象来操作。直接内存不属于 Java 堆,所以它不受堆大小限制,但是它受物理内存大小的限制。

   配置 

    可以通过 -XX:MaxDirectMemorySize 参数来设置最大可用直接内存,如果启动时未设置则默认为最大堆内存大小,即与 -Xmx 相同。即假如最大堆内存为1G,则默认直接内存也为1G,那么 JVM 最大需要的内存大小为2G多一些。当直接内存达到最大限制时就会触发GC,如果回收失败则会引起OutOfMemoryError。

  

猜你喜欢

转载自blog.csdn.net/weixin_40151613/article/details/82284799
今日推荐