第二章(1) Java内存区域与内存溢出异常
2.2 运行时数据区域
Java虚拟机所管理的内存将会包括以下几个运行时数据区域:
2.2.1 程序计数器
是一块较小的内存空间, 可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条要执行的字节码指令,它是程序控制流程的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定时刻,一个处理器(多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程的计数器互不影响,独立存储,我么称这类内存为“线程私有”的内存。
此区域是为一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
2.2.2 Java虚拟机栈
也是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭);
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflow Error异常;
虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用到执行完毕,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表:存放方法参数和方法内部定义的局部变量。存放了编译器可知的各种基本数据类型(boolean, byte, char, short, int, float, long, double)、对象引用和returnAddress类型。局部变量表所需的内存空间在编译时完成分配,在进入一个方法之前已经确定。
操作数栈: 后进先出(Last-In-First-Out)的操作数栈,也可以称之为表达式栈(Expression Stack)。
操作数栈可理解为java虚拟机栈中的一个用于计算的临时数据存储区。
动态连接: 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
方法出口:
2.2.3 本地方法栈
本地方法栈和虚拟机栈的作用很相似,区别是:虚拟机栈为虚拟机执行java方法(字节码)服务,本地方法栈则是为虚拟机使用的本地(Native)方法服务。
2.2.4 Java堆
对于Java应用来说,Java堆(Java Hap)是虚拟机管理的内存中最大的一块。所有线程共享,此内存的唯一目的是存放对象实例。
java世界里,几乎所有的对象实例都在这里分配内存。
所有对象实例以及数组都应当在堆上分配(不是绝对的,因为逃逸分析和标量替换)
(补充一个知识点: 逃逸分析: 逃逸分析(Escape Analysis)允许我们在写出性能较好的代码的同时,能通过恰当的抽象,保证良好的代码风格。允许java编译器在多种情况下优化我们的代码,现在它已经作为默认开启的选项了,可以对代码进行优化, 所以说上的面 “所有对象实例以及数组都应当在堆上分配” 这一句不一定对,经过逃逸分析后,可能做优化,不在堆上分配,可能在栈上分配。)
Java堆是垃圾收集器管理的内存区域,因此也称为“GC堆”
Java堆可以处于物理上不连续的内存空间,但是在逻辑上应该是连续的。
对于大对象(典型的如数组对象),多数虚拟机为了实现简单、存储高效考虑,很可能使用连续的内存空间。
如果Java堆中没有内存完成实例分配,并且堆也无法再扩展了,Java虚拟机会抛出OutOfMemoryError异常。
2.2.5 方法区
是线程共享的内存区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
这个区域内存的回收主要是对常量池的回收和类型的卸载。
如果方法区无法满足新的内存分配时,将抛出OutOfMemoryError异常。
2.2.6 运行时常量池
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
(字面量: 可以理解为实际值,int a = 8
中的8和String a = "hello"
中的hello都是字面量)
(符号引用: 在java中,一个java类将会编译成一个class文件。在编译时,java类并不知道引用类的实际内存地址,因此只能使用符号引用来代替。比如org.simple.People类要引用org.simple.Tool类,在编译时People类并不知道Tool类的实际内存地址,因此只能使用符号org.simple.Tool(假设)来表示Tool类的地址。而在类装载器装载People类时,此时可以通过虚拟机获取Tool类 的实际内存地址,因此便可以既将符号org.simple.Tool替换为Tool类的实际内存地址,及直接引用地址。)
如果无法满足新的内存分配时,将抛出OutOfMemoryError异常。
2.2.7 直接内存
直接内存并不是虚拟机运行时数据区的一部分,