Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”, 墙外面的人想进去, 墙里面的人却想出来。
与C和C++的开发相比,java程序员是比较幸福的,因为不再需要考虑内存的申请与回收,一切内存管理相关工作全部交给了JVM,开发自然会轻松很多,最起码内存泄露问题会大大减少。但是凡事有利弊,内存的自动管理固然简化了开发,也让程序员失去了对程序部分掌控能力,一旦程序出现问题,而这个问题正是由于这个脱离掌控的部分产生的,那么问题就会很难排查甚至无从下手,毕竟垃圾回收对于java程序员来说就是一个黑盒。为了避免这种的情况的发生,学习JVM的内存管理是必要的。
上节说了jvm的内存组成及划分,本节就讲讲jvm 的垃圾回收相关内存。jvm的垃圾回收主要发生在堆区,其他区域虽然也存在内存的回收,但是比较少用,而且思想和算法大同小异,所以以对内存回收作为学习对象
内存分代概念
内存回收是一件比较麻烦的事,处理不好对程序性能影响很大,根据人们的探索,在内存回收时根据对象存活周期的不同将内存划分为几块,分块回收是一种比较好策略。于是开发垃圾收集器的生产商们都采用这个策略将内存分代。jvm一般将内存划分为两部分,新生代(Young Generation)和老年代(Old Generation)(对于Hot Spots虚拟机还存在一个永久代,实际上就是方法区,但是因为实现上有问题,后来被放弃了),新生代保存的是程序新创建的对象,老年代保存的是程序中长时间存在的对象。
注:新生代又存在伊甸园(Eden Spac)和存活区(Survivor Space)的概念,后面的内存回收算法会讲到
内存回收算法
内存回收对程序性能影响比较大,内存回收的算法好坏就显得十分重要,经过几十年发展,内存回收产生了多种算法,每种算法各有千秋,适应不同的场景,下面一一介绍
1)分代回收:内存分代算法是内存分代回收思想的具体实现,它就是声明周期不同的对象分开管理,分代回收,针对不同代对象的特点,回收的时间、策略都可以不同。看起来很简单,但是此算法的却几乎被所有种类的的垃圾收集器使用。
2)标记-清除算法:最基础的收集算法,见名知意,此算法就是将堆中已经失效的对象先标记出来,完成后统一回收这些对象内存。这个算法是最简单算法,但是它是内存回收算法的基础,其他算法都是在此基础上扩展和优化
优点:实现简单
缺点:标记和清楚过程速度都不高, 影响程序效率;内存回收后会产生大量内存碎片,导致在以后分配大对象内存时,明明可用内存还有很多却无法使用
3)复制算法:将内存均分为两块,每次使用其中一块,当其中一块用完了,进行一次垃圾收集,将还存活的对象复制到另一块,再把之前使用过的那块内存全部清理掉。
优点:实现简单,没有内存碎片,也就不用再考虑如何处理内存碎片
缺点:内存的利用率很低,只能使用50%的内存;当对象存活率较高时需要复制的数据量比较大,效率不高
补充:IBM公司研究表明,程序中98%的对象生命周期都很短暂,按照1 : 1的比例划分空间时很浪费的,于是生厂商门门采用一种新策略,将内存分为一块较大的Eden空间和两块较小的Survivor空间,比例一般是 Eden :Survivor1:Survivor = 8:1:1。每次使用Eden和其中一块Survivor ,当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。现在的商业虚拟机都采用这种收集算法来回收新生代。
这样的策略虽然提高了内存的利用率,但是有可能造成Survivor内存不够用的问题,于是就需要额外的内存作为备用,这块额外内存就是老年区。当Survivor内存不足存放还存活的对象,就直接把这些对象寸放到老年区,这就是“分配担保”
4)标记-整理算法:用于老年代内存收集的算法, 因为老年代不想直接使用复制算法浪费50%的内存空间,也没有再额外的内存担保了,就出现了这种新算法。此算法结合了标记-清除算法和复制算法,每次内存收集先标记还在使用的对象,然后将对象复制到老年代内存块的一侧,然后清楚另一侧全部内存。
优点:实现简单,没有内存碎片;内存利用率比较高
缺点:需要标记和复制,效率没有复制算法高
垃圾收集器
1)Serial收集器:最基本最原始的垃圾收集器,新生代垃圾收集,单线程收集器
优点:实现简单高效,处理器cpu和线程数比较少的情况下,此收集器的效率可能比其他收集器的效率更高
缺点:进行垃圾收集时会将用户线程全部暂停(Stop The World),影响用户体验;不能很好的利用多cpu,多线程环境
2)ParNew收集器:Serial收集器的多线程版本
优点:能更好的利用多线程;能与CMS收集器配合工作
缺点:存在Stop The World;存在线程切换,处理器cpu和线程数比较少的情况下,效率不高
3)Parallel Scavenge:多线程收集器,和ParNew 功能非常类似。但是它提供一个特殊功能,可控制垃圾收集时的停顿时间,提升用户体验
优点:能更好的利用多线程;可控制垃圾收集时的停顿时间,提高程序吞吐量
缺点:使用起来比较复杂,实际效果可能不能达到使用者的预期,减少Stop The World时间就需要减少新生代内存大小,这样每次垃圾回收的时间是变少了,但是回收频率却变高了,所以具体情况需要测试才能知道
4)Serial Old:Serial收集器的老年代版本,单线程收集器
优点:实现简单高效,处理器cpu和线程数比较少的情况下,此收集器的效率可能比其他收集器的效率更高
缺点:进行垃圾收集时会将用户线程全部暂停(Stop The World),影响用户体验;不能很好的利用多cpu,多线程环境
5)Parallel Old:Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
优点:能更好的利用多线程;可控制垃圾收集时的停顿时间,提高程序吞吐量
缺点:与Parallel Scavenge类似
6)CMS收集器:并发低停顿收集器,一种以获取最短回收停顿时间为目标的收集器,此收集器的内存收集的部分过程可以和用户线程并发进行。 最大化降低Stop The World的时间
优点:统停顿时间最短,提升用户体验
缺点:由于垃圾收集和用户线程是并发的,势必会导致用户程序可用线程数减少,在线程数本身就比较少的服务器上,性能影响十分严重;CMS实现基于“标记—清除”算法,会产生内存碎片
7)G1收集器:一款面向服务端应用的垃圾收集器,几乎整合了以上垃圾收集器的所有好的设计理念
优点:多线程,充分利用服务器硬件优势,缩短Stop The World时间,同时可以与用户线程并发执行;基于“标记—整理”算法,没有内存碎片;可设置停顿时间,提高系统吞吐率
缺点:出现的比较晚,没有经过太多的商业实验,具体性能不好说