首先,我们都知道,基本上所有对象以及一部分数组都存放在我们的堆里面,如果堆内存用完,就会产生OOM(OutOfMemory),java给我们自动集成了垃圾回收器(System.gc),今天就来总结一下,JVM当中的4中垃圾回收算法,以及7中垃圾回收器。
ps: 垃圾回收算法和垃圾回收器的关系,垃圾回收算法是思想,而垃圾回收器是运用这些算法的一个落地实现。
内存中已经不再被使用到的空间(对象)就是垃圾。
JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。
基本思路就是通过一系列名为:"GC ROOTs"的对象作为起始点,然后开始向下搜索, 如果一个对象到GC ROOTs没有任何引用链相连接是,则说明此对象不可用,如果能遍历到(可达) 就被判定为存活;没有被遍历到的就自然被判定为死亡。
GC算法(引用计数/复制拷贝/标清清除/标记压缩整理)是内存回收的方法论,垃圾收集器就是算法的落地实现。
目前还没有完美的收集器出现,只是针对具体应用最适合的收集器,分别引用
老牌垃圾回收算法,通过引用计算来回收垃圾
引用和去引用伴随加法和减法,影响性能,很难处理垃圾对象的循环引用,java中没有使用
public class Test{
public static void main(String[] args) {
MyObject object1=new MyObject();
MyObject object2=new MyObject();
object1.object=object2;
object2.object=object1;
object1=null;
object2=null;
}
}
class MyObject{
MyObject object;
}
这段代码是用来验证引用计数算法不能检测出循环引用。最后面两句将
object1
和object2
赋值为null
,也就是说object1
和object2
指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。
标记清除的基本思想: 标记清除将垃圾回收分为两个阶段,1.标记阶段,2.清除阶段。在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象,因此,未被标记的对象就是未被应用的垃圾对象。然后在清除阶段,将清除所有未被标记的对象。如下图所示:
容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
标记压缩算法是在标记清除算法的基础上做了一些优化(对存活对象进行移动),和标记清除算法一样,标记-压缩也是从根节点开始,对所有可达对象做一次标记,但之后,它并不简单的清除未标记的对象,而是将所有的存活对象压缩到内存的一端,之后,清除边界外所有的空间。 ps:标记-压缩算法适合用于存活对象较多的场合,如老年代。
记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。
原理:将原有的内存分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复杂到未使用的内存块中,之后,清楚正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
与标记-清除算法相比,复制算法是一种相对高效的回收算法 ---新生代
不适用与存活对象较多的场合,如老年代
最大问题:空间浪费,整合标记清理思想
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间(一般为8:1:1),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
新生代和老年代的区别(阿里面试官的题目):
所谓的新生代和老年代是针对于分代收集算法来定义的,新生代又分为Eden和Survivor两个区。加上老年代就这三个区。数据会首先分配到Eden区 当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。),当Eden没有足够空间的时候就会 触发jvm发起一次Minor GC。如果对象经过一次Minor GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空 间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代 中了,当然晋升老年代的年龄是可以设置的。如果老年代满了就执行:Full GC 因为不经常执行,因此采用了 Mark-Compact算法清理
下图是垃圾收集器的架构图,一般垃圾收集器,都是新生代和老年代搭配这一起用的,下图中的连线表示搭配关系。
- Serial收集器(复制算法)
新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式,可以通过-XX:+UseSerialGC
来强制指定。- Serial Old收集器(标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本(废除)- ParNew收集器(停止-复制算法)
新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。- Parallel Scavenge收集器(停止-复制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。是server级别默认采用的GC方式,可用-XX:+UseParallelGC
来强制指定,用-XX:ParallelGCThreads=4
来指定线程数。- Parallel Old收集器(停止-复制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。- CMS(Concurrent Mark Sweep)收集器(标记-清理算法)
G1垃圾收集器
下图是底层代码表示的垃圾收集器搭配关系。
G1是什么?
是一款面向服务端的收集器,jdk1.7 应用在多处理器和大容量内存环境中, 像CMS收集器一样,能与应用程序线程并发执行。G1收集器的设计目标就是取代CMS收集器。
底层原理
主要改变是,Eden、Survivor、和Tenured等内存区域不再是连续的,而是变成了一个个大小一样的region区域,每个region从1M到32M不等,一个region有可能属于Eden、Survivor或者Tenured内存区域。
区域化内存划分Region,整体编为了一些列不连续的内存区域,避免了全局内存的GC操作。
核心思想是将整个堆内存区域分为大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小,在堆的使用上,G1并不要求对象的存储一定时物理上连续的,只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换,启动时可以通过参数 -XX:G1HeapRegionSize=n可指定分区大小(1M~32M,且必须是2的幂),默认将整个堆划分为2048个分区。 最大支持内存:32MB*2048 = 65536MB = 64G内存。虽然G1算法将堆划分为若干区域(Region),它任然属于分代收集器。
回收步骤:
优点:
1.不会产生很多的内存碎片
为什么?---Region的一部分包含老年代,士大夫G1收集器通过将对象从一个区域复制到另一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了
2.G1在停顿时间上添加了预测机制,用户可以指定期望停顿的时间
为什么?----G1收集器是把整个堆(新生代,老生代)划分成多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域。
常用参数
本文主要是讲了4大垃圾回收算法,7大垃圾收集器。在理解这些知识的前提,我们需要知道,垃圾回收的是发生在堆当中。在了解垃圾回收算法的时候,我们需要掌握什么是GC Root(GC跟对象)、java当中那些可以作为根对象、可达性分析的原理是什么。知道了这些基础概念,我们学习垃圾回收算法的时候就会比较容易了,因为,不管是引用计数、标记清理、标记压缩、还是复制拷贝算法,都会用到可达性分析以及GCRoot。 在学习标记清除、标记压缩整理、复制拷贝算法的时候,需要层层递进,后面一个都是对前面一个做了相关改进,这样一来,这些知识就串起来了。这样学习垃圾收集器的时候就比较轻松了。在学习垃圾收集器的时候,需要明白分代思想是什么,新生代和老年代的区别。因为垃圾收集器是可以分为新生代和老年代区域的。新生代包括 1.串行GC(serial) 2.并行GC(ParNew) 3.并行回收GC(Parallel/ Parallel Scavenge), 老生代包括 1. 串行GC(serial old) 2.并行GC(Parallel Old) 3.CMS并发标记清楚。新生代一般用复制拷贝算法,老生代一般用标记压缩算法。最后单独列出G1算法,要知道G1是通过Region区域来实现的,以及底层实现。这样基本上GC部分的知识就有一个框架在心中了。