深入理解Java虚拟机之内存详解

来源:互联网 发布:mac版永恒战士2存档 编辑:程序博客网 时间:2024/06/08 07:38

Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分为若干不同的区域,包括程序计数器、堆、虚拟机栈、方法区、本地方法栈。


  • 程序计数器
       程序计数器是一块较小的空间,可以看做是当前线程所执行的行号指示器。通过改变这个程序计数器的值来取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要这个计数器来完成。
       每个线程都需要有一个独立的程序计数器,各个线程之间程序计数器互不影响,独立存储,为线程私有的内存。如果正在执行的是Java方法,则程序计数器存储的是正在执行的虚拟机字节码指令的地址,如果是Native方法(),这个程序计数器为空。程序计数器是唯一没有OutMemoryError情况的区域。
       堆是虚拟机中所管理内存中最大的一块,用于保存对象实例,在虚拟机启动时创建,是线程共享的。
       堆可以处于物理上不连续的空间,只要逻辑上连续即可。
       Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为GC堆。现在垃圾收集器基本采用分代收集算法,所以Java堆又可分为新生代,老年代,新生代又可分为Eden空间和Survivor空间

  • 虚拟机栈
       虚拟机栈描述的是Java方法执行的内存模型,每个方法执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法调用及完成,都对应一个栈帧的入栈和出栈过程。虚拟机栈也是线程私有的。
       局部变量表存放了编译期可知的各种基本数据类型,对象引用类型,对象引用类型不等同于对象本身,可能是一个对象的起始地址,或者指向一个代表对象的句柄,或其它与对象相关的位置类型。64位长度的long和double基本数据类型占用2个局部变量空间,其他类型占用一个,局部变量表所需空间在编译期间完成分配。
       如果线程所请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常。如果虚拟机可以动态扩展,扩展时无法申请到足够空间则会抛出OutOfMemoryError异常。
  • 方法区
       方法区用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后产生的代码等数据。
       和Java堆一样不需要连续物理上的内存空间,可以选择固定大小或者可扩展,也可以选择不实现垃圾收集。但数据并非进入这一区域后“永久”存在,这区域的回收目标主要是类型卸载及对常量池的回收。在HotSpot虚拟机中,设计团队将GC分代收集扩展至方法区,或者说使用永久代实现方法区。
       当方法区无法内存分配需要时,会抛出OutOfMemoryError异常。
       运行时常量池:
       属于方法区一部分,Class文件中除了有类的版本、字段、方法、接口等信息外,还有一项信息就是常量池,用于存放编译期产生的各种字面量和符号引用。Class文件的常量池在类加载后存放到运行时常量池中。
(int a=123;其中123为字面量,a为变量。int final b=3.14; b为符号引用。)
  • 本地方法栈
       与虚拟机栈类似,两者区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用的Native方法服务,与虚拟机一样会抛出StackOverflowError和OutOfMemoryError异常。
  • 直接内存
       直接内存并不是虚拟机运行时数据区的一部分,但这部分被频繁引用,也可能导致OutOfMemoryError异常。
在Java1.4中新加入了NIO类,引入一种基于通道与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作。这样能显著提高性能,避免了在Java堆与Native堆中来回复制数据。
       不受Java堆内存限制,但受本机总内存限制。


原创粉丝点击