JVM内存模型程序计数器、虚拟机栈、本地方法栈、堆、方法区、运行时常量池、直接内存

2022-09-07 11:16:32

java内存虚拟机主要分为程序计数器、java虚拟机栈、本地方法栈、java堆、方法区(方法区/运行时常量池)5大部分。

1.程序计数器(Program Counter Register)

    他记录了程序执行的字节码的行号和指令,字节码解释器工作时就是通过改变计数器值来选择下一个要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等。

    由于java虚拟机多线程是通过线程轮流切换CPU时间片的方式来实现的,在任何确定的时刻,一个处理器(对于多核处理来说是一个内核)都置灰执行一条线程中的指令。因此,为了线程每次切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,这块内存也叫“线程私有内存”,其不会出现OutOfMemoryError异常。

2.java虚拟机栈(java Cirtual Machine Stacks)

    java虚拟机栈也是线程私有的,生命周期与线程相同,主要存储基本数据类型(boolean、byte、、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身。可能是一个对象起始地址的引用指针,也可能指向代表一个对象的句柄或者此对象的相关位置)和returnAddress类型(指向了一条字节码指令的地址)。

    其中double、long类型数据会占用2个局部变量空间(slot),其余的数据类型只占有1个。局部变量表所需要的内存空间在编译器完成分配,运行期间不会改变其大小。

    目前大多数虚拟机可动态扩展,如果扩展时无法申请到足够的内存即会抛出OutOffMemoryError异常。

3.本地方法栈(Native Method Stack)

    与java虚拟机栈作用是相似的,他们之间的区别是java虚拟机栈用来执行java方法(也就是字节码),而本地方法栈则为虚拟使用到的native(用来修饰可供其他语言调用的方法,如操作操作系统底层服务的方法)服务,部分虚拟机(譬如 sun HotSpot)直接将虚拟栈和本地方法栈合二为一,与java虚拟机栈相同,也会抛出OutOffMemoryError异常。

4.java堆(java Heap)

    java堆是虚拟机管理的最大的一块内存,堆上的所有线程共享一块内存区域,在启动虚拟机时创建。此内存唯一目的就是存放对象实例,几乎所有对象实例都在这里分配,这一点java虚拟机规范中的描述是:所有对象实例及数组都要在堆上分配。但随着jit编译器的发展与逃逸分析技术逐渐成熟,栈上分配、变量替换优化技术将会导致一些变化,所有对象都分配在堆上也不那么绝对了。

    java堆是垃圾收集器管理的主要区域,也被称为“GC堆”,由于现在收集器基本都采用分代收集算法,所以java堆中还可以分为:新生代和老年代;再细致一些可以分为:Eden空间、From Survivor、To Survivor等,下面是分带收集算法。

    java堆的内存即可固定的大小,不过当前虚拟机都是按照可扩展来实现的(通过-Xmx和Xms控制),当堆达到最大值无法再扩展时抛出OutOffMemoryError异常。

HotSpot虚拟机GC算法采用分代收集算法:

1、一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直到GC到来打破了他们平静的生活。GC会逐一问清楚每个对象的情况,有没有钱(此对象的引用)啊,因为GC想赚钱呀,有钱的才可以敲诈嘛。然后富人就会进入Survivor Space(幸存者区),穷人的就直接kill掉。

2、并不是进入Survivor Space(幸存者区)后就保证人身是安全的,但至少可以活段时间。GC会定期(可以自定义)会对这些人进行敲诈,亿万富翁每次都给钱,GC很满意,就让其进入了Genured Gen(养老区)。万元户经不住几次敲诈就没钱了,GC看没有啥价值啦,就直接kill掉了。

3、进入到养老区的人基本就可以保证人身安全啦,但是亿万富豪有的也会挥霍成穷光蛋,只要钱没了,GC还是kill掉。

分区的目的:新生区由于对象产生的比较多并且大都是朝生夕灭的,所以直接采用标记-清理算法。而养老区生命力很强,则采用复制算法,针对不同情况使用不同算法。

非heap区域中Perm Gen中放着类、方法的定义,jvm Stack区域放着方法参数、局域变量等的引用,方法执行顺序按照栈的先入后出方式

5.方法区(Method Area)

    与java堆一样,是各个线程共享一块内存区域,用于存储虚拟机加在的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然java虚拟机把他标注为堆的一部分,但他也有一个别名“非堆”,用于与java堆区分。

    对于HotSport虚拟机上开发、部署的程序员来说,很多人都更愿意把方法区成为"永久代"(Permanent Generation),进入永久代不代表不被垃圾回收了,只是此部分回收规则相当苛刻,当方法区无法满足内存分配时机抛出OutOffMemoryError异常。

6.运行时常量池(RuntimelyConstant Pool)

    其实方法区的一部分,当前区域用于存储编译器生成的各种字面常量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放。

    运行期间也可能会将常量放到池中,String类的intern()方法利用的比较多,常量池申请不到内存也会抛出OutOffMemoryError异常。

7. 直接内存(DirectMemory)

    他并不是虚拟机运行时数据区的一部分,也不是java迅疾规范中定义的内存区域。但这部分内存也被频繁的使用,而且也可能导致OutOffMemoryError异常。

    由于jdk1.4中心增加了nio类,引入一种基于通道(channel)与缓冲区(Buffer)的IO方式,他可以使用Native函数库直接分配堆外内存,然后通过存储在java堆中的DirectByteBuffer对象做为这块内存的引用进行操作。这样可以显著提高性能,避免java堆和Native堆中来回复制数据。

    虽然他不受java堆大小控制,但是受到本机总内存控制,我们配置虚拟机参数容易忽略,参数空间总和大于本机总内存,导致其动态扩展抛出OutOffMemoryError异常。

  • 作者:小白 2-0-1-9
  • 原文链接:https://blog.csdn.net/weixin_45342958/article/details/105488074
    更新时间:2022-09-07 11:16:32