在JDK1.8以及之前的JDK版本中,堆内存通常被分为三块区域Nursery内存(young generation)、长时内存(old generation)、永久内存(Permanent Generation for VM Matedata),一个对象被创建以后首先被放到Nursery中的Eden内存中,如果存活期超两个Survivor之后就会被转移到长时内存中(Old Generation)中。永久内存中存放着对象的方法、变量等信息。通常如果永久内存不够,我们就会得到OutOfMemoryError异常。
而在JDK1.8之后,一般情况下都不会得到这个错误,原因在于JDK8中把存放元数据中的永久内存从堆内存中移到了本地内存(native memory)中,这样永久内存就不再占用堆内存,它可以通过自动增长来避免在JDK1.8以及之前的JDK版本中中常见的永久内存错误(java.lang.OutOfMemoryError: PermGen)。
JDK1.8之后的Heap主要分为三大块:PSYoungGen(新生代)、ParOldGen(老年代)、Metaspace(元空间),新生代又分为eden(伊甸区)、from和to(幸存区)。
GC算法与种类
Java语言通过可达性分析算法来判断对象是否存活。
通过一系列名为"GC Roots"的对象作为起始点,从这些结点开始向下搜索,搜索走过的路径称为引用链,当一个对象到"GC Roots"没有任何引用链相连,就说明此对象不可用,可以被回收。
如上图所示,object1~object4对GC Root都是可达的,说明不可被回收,object5和object6对GC Root节点不可达,说明其可以被回收。
在Java语言中,可作为GC Roots的对象包含以下几种:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象。(可以理解为:引用栈帧中的本地变量表的所有对象)
2.方法区中静态属性引用的对象(可以理解为:引用方法区该静态属性的所有对象)
3.方法区中常量引用的对象(可以理解为:引用方法区中常量的所有对象)
4.本地方法栈中(Native方法)引用的对象(可以理解为:引用Native方法的所有对象)
第一种是虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的。
第二种是我们在类中定义了全局的静态的对象,也就是使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。
第三种便是常量引用,就是使用了static final关键字,由于这种引用初始化之后不会修改,所以方法区常量池里的引用的对象也应该作为GC Roots。
最后一种是在使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码,因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。
垃圾回收算法
标记-清除算法
标记阶段:找到所有可以访问到的对象做个标记
清除阶段:遍历堆,回收所有未被标记的对象
缺点:涉及大量内存遍历工作,执行性能较低,java程序吞吐量降低/对象被清除之后,被清除的对象留下的内存的空缺位置会造成内存不连续,导致空间浪费
复制算法
该算法简单来说就是将内存一分为二,只使用其中的一份,在垃圾回收时,将正在使用的那份内存中存活的对象复制到另一份内存中,最后将正在使用的内存空间的对象清除
优点:不用考虑内存碎片等情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效
缺点:将内存缩小为原来的一半
压缩算法
标记阶段:找到所有可以访问到的对象做个标记整理阶段:移动所有存活的对象,按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收
优点:弥补了标记-清除算法中,内存区域分散的缺点,也消除了复制算法中,内存减半的高额代价
缺点:不仅要标记所有的存活对象,还要整理所有存活对象的引用地址,效率低于复制算法
现在的虚拟机使用复制算法来进行新生代的内存回收,因为新生代中绝大多数的对象“朝生夕亡 ”,所以不需要将整个内存空间分为两部分,而是分为三部分,一部分伊甸区和两部分幸存区(默认比例8:1:1),每次使用伊甸区和一部分幸存区,垃圾回收会将这两块中存活的对象复制到另外一部分幸存区中,同时清理这两块内存。所以新生代每次可以使用90%的内存。当进行复制算法时,若一部分幸存区不够,则将大对象直接进入老年代
三种算法异同点:
相同点:
1.都基于可达性分析算法来判断一个对象时候应该被回收,支撑根搜索算法可以正常工作的理论依据,就是语法中变量的作用域
2.在GC线程开启时,或者说GC过程开始时,他们都要暂停应用程序(stop the world)
不同点:
效率:复制算法>压缩算法>标记-清除算法
内存整齐度:复制算法=压缩算法>标记-清除算法
内存利用率:压缩算法=标记-清除算法>复制算法
分代收集思想:新生代采用复制算法,老年代采用标记-清除算法或压缩算法