java虚拟机1.运行时数据区

Java技术体系模块图

java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及各自的创建和销毁时间,有的区域随虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。

  • 程序计数器

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

  • java虚拟机栈

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

局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAdress类型(指向一条字节码指令的地址finally)。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

在虚拟机规范中,对这个区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将跑出StackOverflowError异常;如果虚拟机栈可以动态扩展,但扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常

  • 本地方法栈

本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

  • java堆

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

java堆是垃圾收集器管理的主要区域,因此也称为GC堆。从内存回收角度看,现在收集器基本采用分代收集算法,所以java堆还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。从内存分配角度看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区。

不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好的回收内存,或者更快的分配内存。在虚拟机规范中规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的就可以。主流的虚拟机都是按照可扩展来实现的,通过-Xmx(最大值)和-Xms(最小值)控制。默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

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

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

  • 方法区

方法区也是线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然虚拟机规范把方法区描述为堆的一个逻辑部分,但它有一个别名Non-Heap,目的是为了与java堆区分开来

对于HotSpot虚拟机,很多人将方法区称为永久代,本质上这两者并不等价,仅仅是因为HotSpot的设计团队将GC分代收集扩展至方法区,或者说用永久代来实现方法区而已,这样HotSpot的垃圾收集器就可以像管理java堆一样管理这部分内存。原则上,如何实现方法区属于虚拟机实现细节,不受虚拟机规范约束,但使用永久代来实现方法区,容易遇到内存溢出问题。因为永久代有-XX:MaxPermSize的上限,另外有少数方法会因为这个原因导致与其他虚拟机有不同的表现(String.intern())。在目前已经发布的JDK1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出。

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

java虚拟机对Class文件每一个部分(包括常量池)的格式都有严格规定,每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行,但对于运行时常量池,java虚拟机规范没有做任何细节的要求,不同的提供商可以按照自己的需要来实现这个内存区域。

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,例如被用的很多的String.intern()。不过既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

  • 直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也频繁地被使用,而且可能导致OutOfMemoryError异常。在JDK1.4中新加入了NIO类,引入了一种基于通道(channel)与缓冲区(buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆和Native堆中来回复制数据。

显然,本机直接内存的分配不会受到java堆大小的限制,但既然是内存,依然受到本机总内存的限制,在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各内存区域总和大于物理内存限制,从而OutOfMemoryError。

#笔记内容参考:《深入理解java虚拟机》

猜你喜欢

转载自www.cnblogs.com/shanhm1991/p/9906917.html
今日推荐