JVM内存模型,是其垃圾回收的基础,深入理解内存模型,对理解GC机制有很大帮助,为我们写更健壮,更高效的代码打下基础。
JVM虚拟机内存主要包括方法区、虚拟机栈、本地方法栈、堆、程序计数器
这个模块。内存模型如下图:
一、方法区
也成为“永久代”、“非堆”,它用于存储虚拟机加载的类信息、常量、静态变量,
是各个线程共享的内存区域,这一块区域不是很大,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。
运行时常量池:是方法区的一部分,class文件,除了有版本、方法、字段、接口等描述信息外,还有一项信息是常量池
,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放在方法区中。
二、虚拟机栈
描述Java方法执行的内存模型,每个方法被执行的时候,都会创建一个“栈帧”,用于存放局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被执行完的过程,就是对应的栈帧在虚拟机栈的入栈到出栈的过程。生命周期和线程的生命周期相同,虚拟机栈是线程私有的内存区域。局部变量表存放各种基本数据类型(int、float、short等)、对象引用(引用指针,并非对象本身)等。其中,double和float占用两个局部变量表的空间,其他基本数据类型,占用一个局部变量表的空间。 局部变量表的空间,在编译时候完成分配,当进入一个方法后,这个方法在栈帧中分配多大的局部变量是确定的,在运行期间,栈帧不会改变局部变量表的大小空间。
三、本地方法栈
与虚拟机栈类型,区别在于,虚拟机栈是为虚拟机执行Java方法服务的,本地方法栈是为虚拟机执行native方法服务的,本地方法栈是线程私有的内存区域。
四、堆内存
也叫做Java堆、GC堆是JVM虚拟机内存管理中最大的一块,也是被各个线程共享的内存, 在JVM创建时候,该内存区域保存了对象示例及数组(所有new的对象),其大小通过-Xms(最小值)和-Xmx(最大值)参数设置。
根据Java虚拟机规范,Java堆可以是处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
现在的垃圾收集器一般采用分代收集,因此堆内存又分为新生代和老年代。新生代主要存储新创建的对象以及尚未进入老年代的对象。老年代一般存储经历多次新生代GC(minor GC)还未被回收的对象。
新生代:
新生代存储新创建的对象。新生代由Eden和两块大小相同的Survivor space组成(又叫S0、S1或者From、To)构成,Eden和S0、S1的比例一般为8:1,具体大小细节以及分配原因,将在后面的垃圾回收机制
里面介绍。
老年代:
老年代一般存储经历了多次新生代GC还未被回收的对象。老年代的内存区域一般都比较大,主要是为了减少老年代的GC(major GC)的次数(该种类型的GC比较耗时),同时为了防止OOM。
一般进入老年代的对象有以下几种情况:
- 1、经历多次minor GC未被回收的(一般是15次,在虚拟机中对象有个age属性,通常每经历一次minorGC,对象的age属性+1,当age大于15即放到老年第中)
- 2、new的对象过大,而新生代经历了minor GC之后,依然放不下这个对象,那么这个对象将直接被放入到老年代。
- 3、可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。
- 4、如1中所述,新生代的某个age的对象,达到新生代Survivor空间一半以上时,大于或等于这个年里的这些对象将会被移到老年代中去。
五、程序计数器
是虚拟机内存模型中最小的一块区域,它的作用是当前线程所执行字节码的行号指示器,在虚拟机的模型里,字节码解释器就是通过改变程序计数器的值,来选取下一条需要执行的指令,分支、循环、异常处理、线程恢复等都依赖程序计数器的来完成。
其他:直接内存
直接内存不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。在jdk1.4中增加了NIO,引入了通道和缓冲区的IO方式,它可以直接调用native方法分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小(Facebook的图片框架fresco就是用扩展直接内存,减少OOM的操作)。
网友评论