深入理解jvm-读书笔记-java运行时数据区

java 运行时数据区域

java运行时数据区域主要有五部分,堆,java虚拟机栈,本地方法栈,方法区,程序计数器,当然不同jvm有不同的实现方式,但这五部份的所提供的功能一定都会有。
深入理解jvm-读书笔记-java运行时数据区

1.程序计数器

程序计数器只有一小片内存,它可以理解为是当前线程所执行的字节码的行号的指示器,这样虚拟机的字节码解释器工作时虚拟机的其他部件就能通过改变这个计数器的值来选取下一条要执行的字节码指令,像分支,循环,跳转,异常处理,线程恢复等等基础功能都是需要依赖这个计数器的值来完成。还有因为程序计数器是为单个线程服务的,所以它是线程私有的。

2.java虚拟机栈

java虚拟机栈描述的是java方法执行的内存模型,每一次调用方法都会生成一个方法所对应的栈帧,并将其入栈,栈帧里主要包含局部变量表,操作数栈,动态链接,方法出口等信息。方法执行完返回后该栈帧就会被出栈并销毁。java虚拟机栈就是常说的栈内存。

栈帧里的局部变量表里存放了编译期可知的各种基本类型,对象引用以及returnAddress类型(指向一条字节码指令的地址)。

java虚拟机规范里对该区域定义了两种异常状况,分别是*Error 和 OutOfMemoryError。*Error是指线程所引用的栈深度超过了栈本身的深度,而OutOfMemoryError是指虚拟机栈在本身栈的内存不够用的情况下扩容,但如果无法申请到足够的内存就会抛出内存溢出的异常。

3.本地方法栈

本地方法栈的结构与java虚拟机栈的结构是大致相同的,可能抛出的异常也是相同的,只不过在本地方法栈里保存的是java的一些本地方法,这些方法是虚拟机要使用的一些native方法。有的虚拟机可能让本地方法和java方法使用同一个栈内存。

并且由于由于操作系统会在线程切换的时候保存现场环境和恢复环境,这样线程之间的切换就不会影响各个线程之间的方法的执行,所以完全可以将本地方法栈和虚拟机栈设置为线程共享的。

这里之前说错了,把java虚拟机管理的线程跟操作系统所管理的线程混淆了,虚拟机栈跟本地方法栈都是线程私有的,jvm里线程切换好像不存在什么保存环境啥的。

4.堆

java堆是所有线程共享的一块区域,在虚拟机启动的时候分配,堆的唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配内存。

因为堆作为最大的运行时数据区,它的内存的利用率极大的影响了java应用的内存利用率,所以java虚拟机的垃圾回收也就是基本在这里进行。作为线程共享的内存区域,从内存分配的角度来看,java堆可以划分出多个线程私有的分配缓冲区,这样划分的目的是为了更好地进行垃圾回收,以及内存分配。

java虚拟机规范规定java堆可以在物理上是不连续的只要逻辑上是连续的就行,当然这种物理上的不连续不是意味着我们能接受垃圾回收所带来的内存碎片。java堆在实现时虚拟机规范没有对是否是固定大小作出规定,不过当前主流的虚拟机的堆内存都是可扩展的,而当堆中没有内存完成对象的分配,而且堆也无法扩展的时候就会抛出OutOfMemoryError异常。

5.方法区

方法区也是线程共享的,它用于储存虚拟机加载的类信息,常量,静态变量,以及即时编译器编译后的代码等数据。虚拟机规范中把方法区描述为堆的一个逻辑部分,但为了同堆区分,也常被称为非堆。spot hot虚拟机使用分代回收策略中的永久代来实现方法区,不过永久代存在最大-XX:MaxPermSize限制,这样就更容易遇到内存泄漏的问题,而其他不存在永久代的虚拟机的方法区则可以很大程度的扩展,只要不触碰进程可用内存的上限,如32位系统的4GB最大进程内存。不过spothot虚拟机的官方团队也有放弃永久代的规划了。

6.运行时常量池

运行时常量池是方法区的一部分,class文件中的常量池在进行类加载之后就存放在方法区的运行时常量区中,class文件中的常量池存放了编译期生成的各种字面量和符号引用,不过在类加载的时候所产生的直接引用也一般会被放在运行时常量池中。
运行时常量池同字节码文件中的常量池的另一个大的重要特征就是具备动态性,java并不要求常量一定要在编译期生成,,也就是并非只有class文件中的常量池中的内容才能进入运行时常量区,String类的intern()就是利用这种特性所实现的。

7.直接内存

直接内存不属于java虚拟机运行时数据区,也不是java虚拟机规范中的定义的内存区域,但这部分内存也经常会被使用,而且也可能导致OutOfMemoryError异常出现。

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