jvm——内存区域划分

Posted 通凡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm——内存区域划分相关的知识,希望对你有一定的参考价值。

前言:jvm内存划分为多个区域,每个不同的区域分别有着不同的功能和不同的用途,本篇博客旨在说清楚区域的划分、名称、属性以及作用,本篇博客是参考了多篇博客的结果,参考博客会在文章结尾进行说明。

上图中说明了堆中对象的分配来源,对象实例化在堆中,但是其引用可能在方法区(常量实例),或者虚拟机的栈帧中(java方法中的实例化)和本地方法栈中(本地方法对象的实例化),在下面有详细说明。

一.程序计数器

1.定义

  程序计数器,是一块较小的内存区域。它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖程序计数器来完成。

2.特点

  • 线程私有。由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
  • 执行情况说明。如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是native方法,这个计数器值则为空
  • 此内存区域是唯一一个在java虚拟机规范中没有规定任何OOM的区域。

二.java虚拟机栈

1.定义

  虚拟机栈描述的是java方法执行的内存模型:每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

  经常有人把java内存区域分为堆内存和栈内存,这种分法比较粗糙,java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序员最关注的,与对象内存分配关系最密切的内存区域是这两块。其中所指的栈就是这里的栈,或者更具体说是虚拟机栈中的局部变量表部分。

  在java栈中保存的主要内容为栈帧。每一次函数调用,都会有一个对应的栈帧被压入java栈,每一个函数调用结束,都会有一个栈帧被弹出java栈。当函数中存在其他函数的调用时,例如函数1调用函数2,函数2调用函数3,那么函数1会先入栈,生成相应的栈帧;然后调用函数2,在虚拟机栈中生成相应的栈帧,函数3类似;当前正在执行的函数所对应的帧就是当前的帧(位于栈顶),它保存着当前函数的局部变量、中间结果等数据。

  当函数返回时,栈帧从java中被弹出(依次顺序为函数3、函数2、函数1)。java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令,另外一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出

备注:每次函数调用都会生成对应的栈帧,如果请求的栈深度大于最大的可用栈深度时,系统就会抛出stackoverflowerror栈溢出错误。(线程私有)

2.栈帧具体内容

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

1)局部变量表

局部变量表存放了编译器可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和return address类型(指向一条字节码指令的地址)。

return address类型(A给命令于B,b反馈于A,这个时候A即为返回地址。)

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

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

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

2)操作数栈

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

操作数栈也是一个先进后出的数据结构,只支持入栈和出栈两种操作。

把局部变量区的东西拿过来入栈,出栈等等
a =2;
b = 3;
c = a + b;
return c;
c = a +b 时会把局部变量表的a 和 b拿过来入栈,进行运算

3)动态链接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。Class文件的常量池有存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化分为静态解析。另一部分将在每一次的运行期间转化为直接引用,这部分成为动态链接。

4)方法出口

分为正常退出和异常退出,两种退出都要返回带方法被调用的位置,程序才能正常执行,方法返回时可能需要在栈帧中保存一些信息来帮助恢复它的上层方法执行状态。

3.特点

  • 线程私有,它的生命周期和线程一样
  • 在java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出stackoverflowerror异常;当栈在扩展时无法申请到足够的内存,就会抛出outofmemoryerror异常

三.本地方法栈

1.定义

  本地方法栈与虚拟机栈所发挥的作用是类似的,其区别不过是虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的native方法服务(线程私有)。

四.java堆

1.定义

  对于大多数应用来说,java堆是java虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存(线程共享)。

  java堆是垃圾回收器管理的主要区域,因此很多时候也被称为“GC堆”,如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以java堆中还可以细分为:新生代和老生代;再细致一点的有Eden空间,From Survivor空间、To Survivor空间等。不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好的回收内存,或者更快的分配内存。

2.特点

  • java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
  • 当在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

五.方法区

1.定义

  是各个线程共享的内存区域,它用于存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它有一个别名NonHeap(非堆),目的应该是与java堆区分开来。方法区的大小决定了系统可以保存多少个类(最初加载到内存中的类文件就存放在这个地方)(线程共享)。

  运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段,方法、接口等信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

http://blog.csdn.net/olanlanxiari/article/details/8104505

http://www.myexception.cn/j2se/1645341.html

常量池之中主要存放两大类常量:字面量、符号引用。字面量比较接近于java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括下面三类常量:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符

被更新了,自己之前一直以为只有字面量

2.特点

  • 各个线程共享的内存区域
  • 垃圾回收再这个区域比较少出现,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收成绩比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是必要的
  • 根据java虚拟机规范,当方法区无法满足内存分配时,将抛出outofmemoryerror异常

六.直接内存

1.定义

  直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。但是该部分内存在被频繁的使用,而且也可能导致oom异常出现。

  在jdk1.4中新加入了NIO类,引入了一种基于通道(channel)与缓冲区(buffer)的I/O方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆和native堆中来回复制数据。

  显然,本机直接内存的分配不会受到java堆大小的限制,但是受限于本机总内存的大小,同样会产生OOM。

七、参数配置


JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;

控制参数

-Xms设置堆的最小空间大小。
-Xmx设置堆的最大空间大小。
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xss设置每个线程的堆栈大小。

没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制。

老年代空间大小=堆空间大小-年轻代大空间大小

总结

以上就是jvm内存分配的全部内容,本文参考了一下文章:
(第一篇找不到了,对博主表示感谢,本文主要内容来自这篇博客)
https://blog.csdn.net/steady_pace/article/details/51254740
http://www.360doc.com/content/14/0925/13/1073512_412236522.shtml

以上是关于jvm——内存区域划分的主要内容,如果未能解决你的问题,请参考以下文章

JVM的内存区域划分

JVM 内存区域与GC

JVM的内存区域划分

JVM总结

JVM的内存区域划分

JVM系列 - JVM内存区域详解