一.jvm运行时内存区域
包含堆,虚拟机栈,本地栈(调用native方法时用到),方法区(perm区),程序计数器。
假设32位操作系统,这时系统限制每个进程大小为2G。这样上述这些区域(对于本地栈及程序计数器来讲,是无法设置的,hotspot有提供-Xoss参数用于设置本地方法栈。但实际是无效的)可以用相应参数设置,共同划分全部2G内存。
注意还有一块直接内存,不属于JAVA运行时区域。但是它的空间用得太多的话,还是受限于物理内存和虚拟内存总大小,因此也会抛出OutOfMemory。典型的redis中有大量使用直接内存。redis负责使用System.gc()告诉虚拟机要回收直接内存(触发FULL GC)。如果将 DisableExplicitGC打开,这时如果大量用直接内存将发生 OutofMemmory Directory Memonry。原因是虚拟机只会在FULL GC的时候触发对直接内存的清理。
二.垃圾回收
垃圾回收围绕两个问题:
1.怎样认为一个对象是该回收了的对象?如何找出?
2.找出之后,如何做到有效清除,并使内存碎片最少,回收效率更高?
对于第一个问题,我们很自然的想到引用计数,但引用计数存在循环引用的问题。
还有一种就是根搜索算法,也就是说从当前Root区域开始(使用到对象的区域,如线程栈,方法区中静态引用,常量引用等)标记出所有可达的对象。
最后那些没有被标记的对象就认为是需要回收的对象。
现在主流虚拟机实现用的都是这种方法。
对于第二个问题,找出之后,如何做到有效清理,内存碎片最少,回收效率更高?
首先要回收快,比如给你一个8G的内存区域,这时每次垃圾回收都要扫描8G内存必定使回收效率很低。
虚拟机根据对象存活期不同的特点,想到将区域分为年轻代和老年代。这时假设将年轻代和老年代都是4G,这时可以分别对这两块区域进行回收。
年轻代中对象生命周期短,对它进行回收效益比老年代的大。
考虑只将年轻代分成一块4G会有什么问题?
这时你会发现当对年轻代做完垃圾清除后,会有很多碎片存在(由于很多对象被清除了)。
整理碎片是相当耗时的一个过程。
那怎么解决?
虚拟机将年轻代分为eden及两块survisor区域。每次只使用eden及一块survisor。
每次新建的对象先进入eden区,如果这时engen区不够放了,这时触发minor gc。将扫描eden及一块survisor。将要回收的对象清理掉,还存活的放入到另一块survisor。这样每次回收后对于eden区都不会有内存碎片。
上述讲的对于年轻代的垃圾回收使用的就是标记-复制算法(缺点是需要一块备用区域)。
对于老年代来讲,如果还使用标记-复制算法,这时需要另一块区域作为备用。
这时候由于老年代对象存活率高,因此每次回收后,需要大量的对象拷贝到备用区域,不太合算。
因此对于老年代来讲,使用的是标记-清除算法(或标记整理),即先找出所有要清理的对象,然后清除掉。
这时必然会有碎片在,因此对于像CMS这样的并行老年代回收算法,可以设置多少次FULL GC后触发一次内存整理。
下面列下现有的JVM垃圾回收器:
对于年轻代有:
serial(单线程,回收时停止所有用户线程),
par New(多线程,停止所有用户线程)
parallel scavange(多线程,停止所有用户线程,与 par New类似。但可以控制响应时间,通过缩小年轻代大小,进而减少回收空间来达到)
对于老年代有:
serial old(单线程,停止所有用户线程)
parallel old(多线程,停止所有用户线程。通常与parallel scavange一起使用)
CMS(多线程,部分回收阶段不需要回收用户线程,达到可控的响应时间)
G1
由于par New与CMS采用了全新架构,因此par New与CMS一起使用。
parallel scavange与parallel old通常一起使用。
jvm基础理解