Skip to content

jvm中java内存模型 #4

Open
Open
@rockcoder23

Description

@rockcoder23

一. 引言:

众所周知java的内存管理是由jvm来代劳的,这使得程序员在敲代码的时候简单了很多,但是一旦出现了内存泄漏或者溢出方面的问题,如果不了解虚拟机的管理内存的原理,那排错起来将会很艰难。

二.java内存模型:

jvm在执行java程序的过程中会把内存划分为若干个不同的数据区域,这些区域的生命周期和作用各不相同。根据《java虚拟机规范(第二版)》jvm把所管理的内存划分爲如下图几个区域:

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

作用: 程序计数器是一块较小的内存空间,它是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。为了线程切换后能恢复到正确的执行位置,每条线程都需要由一个独立的程序计数器,各个线程之间的计数器互不影晌,独立存储,是“线程私有”的内存。

生命周期: 其生命周期与线程生命周期一致。

  1. java虚拟机栈(java Virtual Machine Stacks)

作用: 每个方法执行的时候都会开辟栈空间,我们称之爲“栈帧”。栈帧主要用于存储方法体内的局部变量表,操作数栈,动态链接,方法出口等信息。每个方法调用完后,对映着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。

生命周期: 属于“线程私有”的内存,其生命周期与线程生命周期一致。

异常: jvm规范中规定了这个区域的两种异常:当线程请求的栈深度大于虚拟机所允许的深度时,将抛出StackOverflowError异常。栈深度大多数情况下达到1000-2000完全没问题(java虚拟机栈大小/每个栈帧大小),对正常的方法调用这个深度是够用了,所以出现了这个异常,一般可能会是递归时进入了死循环导致。 另一种是如果虚拟机栈可以动态扩展(一般都支持),当扩展时无法申请到足够的存储器时就会抛出OutOfMemoryError异常。

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

与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。由的虚拟机(譬如Sun HotSpot虚拟机) 直接把本地方法栈和虚拟机栈合二爲一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError异常和OutOfMemoryError异常。其生命周期也是与线程一致,属于线程私有。

  1. java堆(java Heap/ GC堆)

作用: 对于大多数应用来说,java堆是jvm管理的内存中最大的一块。是被各个线程共享的内存区域,在虚拟机创建时啓动。此内存区域的唯一目的就是存放对象实例,几乎所有的实例对象和数组都在这里分配。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G,-Xmx为JVM可申请的最大内存,默认为物理内存的1/4但小于1G,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。

由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代。新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代GC(Minor GC)任然存活的对象。但是无论如何划分,都与存放内容无关,无论哪个区域存的都是实例对象。这样划分只是爲了更好的分配内存和回收内存

新生代: 程序新创建的对象都是从新生代分配内存,新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成,可通过-Xmn参数来指定新生代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及Survivor Space的大小。

老年代: 用于存放经过多次新生代GC任然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:①.大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。②.大的数组对象,切数组中无引用外部对象。老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。

生命周期: 随jvm的啓动而创建,随jvm的关闭和消亡。是线程共享的区域。

异常: java虚拟机可以处于不连续的内存空间,只要逻辑上是连续的即可,就像我们磁盘空间一样。如果堆没有内存完成实例饿分配,并且也无法扩展时,会抛出OutOfMemoryErro异常。

  1. 方法区(Method Area)

也称"永久代” 、“非堆”, 它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。 运行时常量池:是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

  1. 直接内存(Direct Memory)

直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions