JVM运行数据区(三)

Runtime Data Area主要包括五个部分:

  • Heap(堆)
  • Method Area(方法区域)
  • VM Stack(虚拟机栈)
  • Native Method Stack(本地方法栈)( 在Sun的HotSpot虚拟机中VM Stack和Native method stack是合并到一起的)
  • Program Counter(程序计数器)

Heap和Method Area是被所有线程的共享使用的,而Vm Stack, Program Counter和Native Method Stack是以线程为粒度的,每个线程独自拥有。

虚拟机栈 VM stack

Java虚拟机栈也是线程私有的(每个线程都会拥有的),它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行时都会同时创建一个栈帧(Stack Frame)用于存储局部变量表(基本类型)、操作栈、动态链接(引用对象)、方法出口等信息。
每一个方法被调用直至执行完成的过程,就对应着一个栈帧(栈中的每一个元素就被称为栈帧)在虚拟机栈中从入到出栈的过程,每当线程调用一个方法的时候就会向方法栈压入一个新帧。
栈存取速度比堆要快,仅次于寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

本地方法栈 Native Method stack

本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法服务的,而本地方法栈则是为虚拟机使用到的本地方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。例如Sun HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一。

程序计数器 Program Counter Register

程序计数器是是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器完成。Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程”私有的内存。

方法区 Method Area

方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息等。当开发人员在程序中通过Class对象中的getName等方法来获取信息时,这些数据都来源于方法区域。
方法区域也是全局共享的,因此会涉及到多线种访问的同步问题,方法区在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出JAVA.lang.OutOfMemoryError:PermGen full的错误信息。
在Sun JVM中这块区域对应的为Permanet Generation,又称为持久代,Permanet Generation实际上并不等同于方法区,只不过是Hotspot JVM用Permanet Generation来实现方法区而已,有些虚拟机也没有PermSpace而是用其他机制来实现方法区。

方法区内的常量池 - 运行时常量池(Runtime Constant Pool)

运行时常量池是方法区(永久代)的一部分。Class文件中除了有类的版本,字段、方法、接口等描述信息外,还有一项信息是常量池(Class文件的常量池),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入方法区运行时常量池中。这种特性被开发人员利用得比较多的便是String类的intern()。运行时常量池既然是方法区的一部分,自然也会受到方法区内存的限制,当常量池无池再申请到内存时会抛OutOfMenmoryError异常。

堆空间 Heap

堆空间是Java对象生死存亡的地区,Java对象的出生,成长,死亡都在这个区域完成。 Java程序在运行时创建的所有类实例或数组都放在堆中。
堆中的变量所需的存储空间只有在运行时创建了对象之后才能确定。Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
每一个Java程序独占一个JVM实例,一个JVM实例只存在一个堆空间,因些每个Java程序都有它自己的堆空间,它们不会彼此干扰。
同一个Java程序的多个线程都共享着同一个堆空间,所以就需要考虑多线程访问对象(堆数据)的同步问题。
Sun的HotSpot虚拟机对于堆内存共划分为二大部分:年轻代/新生代(Young Generation)、老年代(Old Generation)。

Young(年轻代)

JVM规范中的Heap的一部份, 年轻代又分三个区:一个Eden区,两个Survivor区。

伊甸园( Eden space)

Java堆空间中的大部分对象在此出生,该区的名字因此而得名。也即是说当你的Java程序运行时,需要创建新的对象时,JVM都将在该区为你创建一个指定的对象供程序使用。

幸存者0区( Survivor 0 space)和幸存者1区( Survivor1 space)

当伊甸园的空间用完时,程序又需要创建对象;此时JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的还存活的对象移动到幸存者0区或1区。幸存者两个区就是用于存放伊甸园垃圾回收时所幸存下来的Java对象。Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。(总有1个是空,和另一个Survivor复制到此Survivor意思并不冲突)

Tenured(老年代)

在年轻代中经历了多次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为老年代中存放的都是一些生命周期较长的对象。另外一些大对象也会直接进入老年代,可以通过设置JVM参数来指定多大对象直接进入老年代(参数为-XX:PretenureSizeThreshold=1024,单位为字节)。

# JVM, Java

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×