JVM内存
JVM内存模型
2.1 运行时数据区域图
2.2 程序计数器
**线程私有。
程序计数器是一块较小的内存空间,记录正在执行的虚拟机字节码地址,可以当做程序正在执行字节码的行号指示器,字节码解释器工作就是通过改变这个程序计数器的值来选取下一条需要执行的字节码指令,程序的分支、循环、判断等功能都依赖于它完成。
此内存区域是java虚拟机规范中唯一一个没有规定OutOfMemoryError的内存区域。**
2.3 java虚拟机栈
2.3.1 概述
**线程私有。
虚拟机栈描述的是java方法的内存模型,每个方法执行同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
从方法调用直至执行完成的过程,对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
可以通过 -Xss参数指定java虚拟机栈的大小。**在 JDK 1.4 中默认为 256K,而在 JDK 1.5+ 默认为 1M。
2.3.2 异常
1. 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
2. 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
2.4 本地方法栈
2.4.1 概述
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
2.5 堆
2.5.1 概述
**线程共享。
java堆是java虚拟所管理内存中最大的一块,几乎所有的对象都在这分配内存。
java堆是垃圾收集器管理的主要区域,因此堆又称”GC堆”(Garbage Collected Heap)。现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法。可以将堆分成两块:
1.新生代(Young Generation)
2.老年代(Old Generation)
在JDK1.6中新生代与老年代默认空间比例为1:2。**
采用复制回收方法的垃圾收集器常将新生代分为三部分,按8:1:1的比例分为eden:survivor:survivor。
2.5.2 异常
可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
2.6 方法区
2.6.1 概述
**线程共享。**
用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码等数据。
2.6.2 异常
和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常
2.6.3 扩展
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
**HotSpot **虚拟机把它当成**永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。**
2.7 运行时常量池
运行时常量池是方法区的一部分。
Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。
除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
2.8 直接内存
在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。