0%

Java内存区域

Java内存分布虽然对于入门的Java初学者来说是透明的,上课念PPT的老师也永远不会跟你提及这些概念,顶多在继承封装多态上让你背一些概念性的东西,但是都说知其然知其所以然,作为Java虚拟机知识体系基础的Java内存是如何进行管理的呢?理解了Java虚拟机的运行原理,对于排除代码BUG和程序性能优化有非常大的好处
内存分布

程序计数器

类似于CPU内部的PC寄存器,保存着字节码指令地址.程序需要通过程序计数器的值来选取下一条需要执行的字节码指令.程序的分支,跳转,循环,异常处理,线程恢复都得靠程序计数器来完成.程序计数器保证了JAVA各线程执行时的井然有序,所以是线程私有的,各个线程独立存储.

  • 如果执行的是Java方法,程序计数器存储的是正在执行的字节码指令的地址
  • 如果正在执行的是Native方法,程序计数器为空(Undefined)
  • 此内存区域是唯一一个没有规定任何OutOfMemoryError情况的区域

Java虚拟机栈

Java虚拟机栈同样是线程私有的,是方法执行时的结构.描述的是方法执行时的内存模型.每个方法执行时都会创建栈帧,用于存储方法的信息.方法的调用和返回过程,对应着栈帧的入栈和出栈过程

栈帧包含信息包括局部变量表,操作数栈,动态链接,和方法的返回地址

如果线程请求的栈深度超过了虚拟机允许的深度,会抛出StackOverflowError异常,如果允许动态扩展的栈在扩展时无法申请到足够的内存,会抛出OutOfMemoryError异常。

局部变量表简介

局部变量表存放方法参数和方法内部局部变量。在编译成Class文件时,在方法的Code属性中的max_locals数据项中就确定了该方法局部变量表的最大容量,局部变量表以变量槽(Slot)为最小单位。详细内容会再进行专门介绍。

操作数栈简介

操作数栈是一个后进先出的栈结构,最大深度在编译时写入Code属性中的max_stacks数据项中。方法开始执行时为空,方法内部的算术运算或者调用其他方法的时候就是通过操作数栈来进行参数传递的。与汇编语言中的CPU栈概念模型类似。

动态链接简介

每个栈帧都包含指向运行时常量池中该栈帧所属方法的引用,Class文件的常量池中存在大量的符号引用,字节码中方法调用指令就以常量池中指向方法的符号引用作为参数。

在类加载阶段转化为直接引用,这种转化成为静态解析。

在每一次运行期间转化为直接引用,这部分成为动态链接。

方法返回地址简介

方法一定是会执行完成的。有两种方式结束方法的执行。一是正常结束,调用者的PC计数器的值可以作为返回地址。二是异常退出,返回地址通过异常处理器表来完成。

方法退出等同于把当前栈帧出栈。可能执行的操作有,恢复上层方法的局部变量表和操作数栈,将返回值写入调用者栈帧的操作数栈,调整PC计数器的值指向方法调用指令的后一条。

本地方法栈

本地方法栈与虚拟机栈发挥作用相似,其中执行的是虚拟机使用到的Native方法。各个虚拟机可能会不同,Sun HotSpot虚拟机就直接将本地方法栈和虚拟机栈合二为一。与虚拟机栈相同,本地方法栈也会抛出StackOverflowError和OutMemoryError异常。

Java堆

Java堆是所有new对象的存储地,是Java虚拟机所管理内存中最大的一块。后续将要学习的内存回收算法就是与这块内存有关的。Java堆是所有线程共享的内存区域。Java虚拟机规范描述:所有的对象实例以及数组都要在堆上进行分配。

Java堆是GC管理的主要区域,可以细分为新生代,老年代。新生代是那种频繁被创建和销毁的对象的存放地,可能每次进行垃圾收集时就会有大批量的对象被清除。老年代的对象则存活率高,新生代的对象如果达到了老年代的标准,也会被划分到老年代,大对象创建时也会直接分配到老年代。具体细节会在GC回收中再总结。

方法区

方法区和堆一样也是线程共享的区域。用于存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等)。Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不进行垃圾搜集。这个区域的垃圾搜集行为比较少见。方法区不够大时,会抛出OutOfMemoryError异常。类信息如下,详细内容会在Java类加载机制中介绍。

类信息

  • 类型全限定名。
  • 类型的直接超类的全限定名(除非这个类型是java.lang.Object,它没有超类)。
  • 类型是类类型还是接口类型。
  • 类型的访问修饰符(public、abstract或final的某个子集)。
  • 任何直接超接口的全限定名的有序列表。
  • 类型的常量池。
  • 字段信息。
  • 方法信息。
  • 除了常量意外的所有类(静态)变量。
  • 一个到类ClassLoader的引用。
  • 一个到Class类的引用。

运行时常量池

Class文件中有一项信息存储编译期生成的各种字面量和符号引用,也就是常量池,这部分内容在类加载后会进入方法区的运行时常量池存放。运行时常量池具有动态性,并不要求运行时常量池严格和Class文件中的常量池内容一致,运行期间也能将新的常量放入池中,比如String类的intern()方法.

运行时常量池隶属于方法区,同样在内存不够大时抛出OutOfMemory异常。