JVM内存区域管理

        初步学习JVM,在网上找到许多资料,感觉有点凌乱,不方便自己以后回过头来重看,就把别人写得好的文章或书籍上写得好的章节,用自己的话描述一下。本文以下内容主要参考了《深入理解java虚拟机:JVM高级特性与最佳实践》一书以及《JVM详解》电子书。

 

运行时数据区域

JVM执行Java程序的过程中,会使用到各种数据区域,这些区域有各自的用途、创建和销毁时间。根据《Java虚拟机规范(第二版)》(下文称VM Spec)的规定,JVM包括下列几个运行时数据区域:

JVM内存区域管理_第1张图片

JVM内存区域管理_第2张图片

 

 其中程序计数器、JAVA栈、本地方法栈3个区域随线程而生,随线程而死。

 

程序计数器


 程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

  由于JAVA虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定时刻,一个处理器(对于多核处理器是一个内核)只会执行一个线程中的指令。因此为了线程切换后能恢复正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间的程序计数器互不影响,独立存储,我们称这类内存区域为“线程私有”内存。

  如果线程正在执行的是一个JAVA方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的Native方法,这个计数器 值则为空(Undefined)。此内存区域是唯一一个JAVA虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

 

JAVA虚拟机栈

        与程序计数器一样,VM栈的生命周期也是与线程相同。VM栈描述的是Java方法调用的内存模型:每个方法被执行的时候,都会同时创建一个帧(Frame)用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。在后文中,我们将着重讨论VM栈中本地变量表部分。
        经常有人把Java内存简单的区分为堆内存(Heap)和栈内存(Stack),实际中的区域远比这种观点复杂,这样划分只是说明与变量定义密切相关的内存区域是这两块。其中所指的“堆”后面会专门描述,而所指的“栈”就是VM栈中各个帧的本地变量表部分。本地变量表存放了编译期可知的各种标量类型(boolean、byte、char、short、int、float、long、double)、对象引用(不是对象本身,仅仅是一个引用指针)、方法返回地址等。其中long和double会占用2个本地变量空间(32bit),其余占用1个。本地变量表在进入方法时进行分配,当进入一个方法时,这个方法需要在帧中分配多大的本地变量是一件完全确定的事情,在方法运行期间不改变本地变量表的大小。
      在VM Spec中对这个区域规定了2中异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果VM栈可以动态扩展(VM Spec中允许固定长度的VM栈),当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。

本地方法栈
       本地方法栈与VM栈所发挥作用是类似的,只不过栈为虚拟机运行VM原语服务,而本地方法栈是为虚拟机使用到的Native方法服务。它的实现的语言、方式与结构并没有强制规定,甚至有的虚拟机(譬如Sun Hotspot虚拟机)直接就把本地方法栈和VM栈合二为一。和VM栈一样,这个区域也会抛出StackOverflowError和OutOfMemoryError异常。

 JAVA堆

      对于绝大多数的应用程序来说,JAVA堆是JVM所管理的内存中最大的一块。JAVA堆也是所有线程共享的内存区域,在虚拟机启动的时候创建。此内存区的唯一目的就是存放对象实例,几乎所有对象实例都在这里分配内存。

     JAVA堆是垃圾收集管理器管理的主要区域,因此很多时候也称作"GC堆"。如果从内存回收的角度看,由于现在收集器基本都是采用分代收集算法,所以JAVA堆中还可以细分为:年轻代和老年代。

方法区

    在JVM中,被装在的class信息存在Method Area区中。当虚拟机装载某个类型时,它使用类加载器定位相应的class文件,然后读入这个class文件内容,提取其中类型信息,并将这些信息保存到方法区中。该类型中的类(静态)变量同样也存到方法去中。和JAVA堆一样也是,多线程共享的。

   运行时常量池,也是方法区中的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面和符号引用,这部分内容将在类加载后存放在方法区的运行时常量池中。比如String s="a",则"a"会放入常量池中。

方法区保存的信息:

这个类型的全限定名。这个类型的直接超类的全限定名(除非这个类是java.lang.Object,它没有超类)这个类型是类类型还是接口类型。这个类型的访问修饰符(public,abstract或final的某个子集)任何直接超接口的全限定名的有序列表。该类型的常量池。字段信息。方法信息。除了常量以外的所有类(静态)变量。一个到类ClassLoader的引用。一个到Class类的引用。
public class A1 {
	public static String a1="abc";
	public static Integer Int1=100;
}


public class A2 {
	public static String a2="abc";
	public static Integer Int2=100;
	public static void compare(){
		String s="abc";
		Integer i=100;
		System.out.println(A1.a1==A2.a2);
		System.out.println(A1.a1==s);
		System.out.println(A2.a2==s);
		
		System.out.println(A1.Int1==A2.Int2);
		System.out.println(A1.Int1==i);
		System.out.println(A2.Int2==i);
	}
	public static void main(String[] args){
		compare();
	}
}


运行结果

true
true
true
true
true
true

 

 

 

 

  

JVM内存区域管理_第3张图片

     

 

JVM内存区域管理_第4张图片

 

1.Young年轻代(新生区)

      年轻代(是属于java堆的一部分)分3个区:1个Eden区(有人叫伊甸园,因为所有对象实例都在这出生),2个survivor幸存区。大部分对象都在Eden中生成。当Eden区满时,GC会把Eden中不存活的对象(就是该对象没有任何其他对象引用它了)销毁,把还存活的对象复制到survivor 0区(为了方便描述,2个survivor,用0,1区分下 ),如果survivor 0 去也满了,GC把servivor 0不存活的对象销毁,则把survivor 0区中还存活的对象复制到survivor 1区中,如果survivor 1也满了,GC把servivor 1不存活的对象销毁,把survivor 1中还存活的对象复制到养老区Tenured中去,如果Tenured也满了,则GC把Tenured不存活的对象销毁。如果Eden,servivor,Tenured都满了,则JVM会报错:java.lang.OutOfMemoryError:Java heap space).也就是堆空间没有空间来创建新对象了。

     2.Tenured年老代(养老区)

    年老代也是java堆的一部分。这区存放的都是从年轻代还存活的对象复制过来的,这里存放的对象的生命周期都是较长的。一般如果系统中使用了Application级别的缓存,缓存中的对象往往会被转移到这里。

     3.持久代

     持久代其实就是方法区Method Area。主要存储class,method,filed对象,这部分空间一般不会溢出,除非一次性加载很多类。

 

     GC做Young GC时,只是回收堆中的不存活对象,不对Perm区(方法区)做GC。如果Perm满了,需要动态扩张的话,会触发一次Full GC,对Perm中不存活的对象进行销毁,所以常量池中的对象也并不是一定不会GC。

你可能感兴趣的:(JAVA基础)