本篇博客大部分内容都摘抄自周志明写的《深入理解java虚拟机》,这本书很好,推荐!
通过上一篇博客Java虚拟机内存模型我们知道了几乎所有的对象都是在堆内存中创建,栈中保存了局部的引用变量,方法区中保存了类的信息和一些常量。而栈中栈帧随着方法执行的完成自动弹出,不需要我们清理垃圾。因此,我们重点需要关注方法区和堆中的内存。
Java虚拟机中的垃圾是什么
首先我们应该明白哪些数据是垃圾。
(1)对于堆中的对象,如果没有引用指向这个对象,这个对象对程序的运行将不会产生影响,因此变为垃圾。
(2)对于方法区中加载的类信息,如果该类的所有实例都已经被回收,加载该类的类加载器已经被回收,该类的class对象没有在任何地方被引用,那么这个类信息就可以被回收。
(3)方法区中的常量池,如果没有任何引用指向常量池中的某个常量,那么这个常量就可以被回收。
怎么获取所有的垃圾
我们知道怎么判断一个对象是不是垃圾,那么怎么在程序中实现呢?怎么快速的获得所有的垃圾?
引用计数法
给对象添加一个引用计数器,每当一个引用指向这个变量, 引用计数器就+1;当引用失效的时候,引用计数器-1;当引用计数器数值为0的时候,这个对象就可以被回收了。
Python、FlashPlayer等一些脚本语言都使用了引用计数法来管理内存。但是引用计数法有一个问题,当两个对象相互引用的时候,引用技术永远也不为0,不能被回收。
可达性分析
在主流的商用程序语言(java、c#)的主流实现中,都是通过可达性分析来判断对象是否存活的。这个算法的基本思想就是通过一系列称为“GC roots”的对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链向量的时候(用图论的话来说,就是从GC Roots到这个对象不可达)时,这个对象是不可用的,可以被回收。
image.png
在java语言中,可作为GC Roots的对象包括以下几种
1.虚拟机栈中的引用的对象
2.方法区中静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI引用的对象
怎么回收垃圾
HotSpot虚拟机将堆内存分为两部分,分别是新生代和老生代(默认新生代占1/3堆内存,老生代占2/3堆内存),将方法区称为永久代。
java中不同对象的生命周期不一样,某些对象的生命周期很短(比如局部引用变量指向的对象,他的生命周和和方法的生命周期一样),对于静态变量指向的对象,他的生命周期就比较长了。我们把生命周期较短的对象放到新生代,生命周期较长的对象放到老生代或者永久代。
垃圾收集算法
(1)标记-清除算法
image.png
标记清除算法分为两个阶段:标记和清除。首先标记出所有需要回收的对象,标记完成后同一回收所有被标记的对象。
适合老生代的垃圾回收。
缺点:时间效率较低,会产生大量不连续的碎片内存。
(2)复制算法
image.png
将可用内存分为两块,每次只使用一块。当一块内存用完后,将所有存活的对象复制到另外一块内存中,然后把之前使用的那一块清空。
适合新生代的垃圾回收。
优点:不需要考虑碎片内存
缺点:内存利用率较低
在HotSpot虚拟机中,又把新生代分为了一块Eden和两块Survivor,Eden和Survivior的大小比例是8:1。每次使用一块Eden和Survivor,每次垃圾回收把Eden和Survivor中存活的对象复制到另外一块Survivor中,如果Survivor放不下,将放入老生代。
(3)标记整理算法
image.png
是在标记清除法基础上做了优化,把存活的对象压缩到内存一端,然后直接清理掉端边界以外的内存。
大多用于老年代的垃圾回收。
分代收集
1、根据对象存活周期的不同将内存划分为几块。
2、一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
3、在新生代中,每次垃圾收集时都发现有大批对象死去(回收频率很高),只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
其中,新生代又细分为三个区:Eden,From Survivor,To Surviver,比例是8:1:1
4、新生代放不下的对象可以通过分配担保机制放入老年代。
4、老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
内存分配与回收策略
1、对象优先在Eden上分配,当Eden中内存不足时,会触发一次新生代GC(Minor GC)
2、新生代GC使用的是复制算法,Survivor中不足以放置所有存活的对象时,一部分对象通过分配担保方法进入老生代。
3、大对象直接进入老生代。可以设置PretenureSizeThreshold,对象内存大于这个阈值的直接进入老生代。
4、通过给每个对象设置年龄计数器,每一次GC都+1,当超过一定年龄后对象进入老生代。年龄阈值可以通过MaxTenuringThreshold设置。
几种垃圾回收器
我们已经知道了新生代和老生代会使用不同的垃圾回收算法。每个区域都有一些垃圾回收器
image.png
上面有7中收集器,分为两块,上面为新生代收集器,下面是老年代收集器。如果两个收集器之间存在连线,就说明它们可以搭配使用。
Serial
是一个串行垃圾回收器,使用使用复制算法,负责新生代的回收。在它执行回收任务的任务的时候,其他线程都应该暂停,知道它收集完成。
parNew
是Serial的多线程版本,使用复制算法,负责新生代的回收。
Parallel Scavenge
多线程收集器,使用复制算法,负责新生代的回收。Parallel Scavenge的特点时它的关注点和其他收集器不同,CMS等收集器的关注点是尽可能缩短垃圾收集是用户线程的暂停时间。Parallel Scavenge收集器的设计目的达到一个可控制的吞吐量。(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。)
Serial Old
Serial的老生代版本,使用标记整理算法。
parallel Old
Parallel Scavenge的老生代版本,使用标记整理算法,多线程。
CMS
是一种以获取最短回收暂停时间为目标的收集器,适合需要较快相应速度的服务器中。使用标记整理算法,可分为初始标记、并发标记、重新标记、并发清除四个阶段。清理过程中耗时最长的并发标记和并发清除过程都可以与用户线程一起工作。
G1
G1垃圾收集器是当今收集器技术发展的最前沿成果之一。G1是一款面向服务端应用的垃圾收集器。G1具备并行与并发、分代收集、空间整合、可预测停顿等特点。
网友评论