jvm怎么知道堆里面的对象是无用数据,有两种方式:
1.引用计数法:
每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
2.跟查找GC Roots
(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,即为不可达对象。
jvm的三种基本算法
1.1 标记-清除算法(Mark-Sweep)
一块内存中的,将要回收的数据,标记 ,直接清除
1.2 复制算法(Copying)
将一片内存 中的数据直接复制到另一块内存中,然后清理掉之前的内存区域
1.3标记-整理算法(Mark-Compact)
将内存中的数据,标记集中移动到内存的一边,进行清理
1.4分代收集算法
注,这个算法是物理和逻辑上将内存堆划分年轻代,老年代,根据不同代中的对象清除时候使用什么算法来提出的一种算法
算法是GC的垃圾回收的策略,而垃圾收集器,就是根据算法来进行垃圾处理的具体实现
2.1 Serial收集器
Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器;JDK1.3.1前是HotSpot新生代收集的唯一选择;Serial收集器到JDK1.7为止,它依然是JAVA虚拟机运行在Client模式下的默认新生代收集器。它也有着优于其他收集器的地方:简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本上不会再大了),停顿时间完全可以控制在几十毫秒最多一百多毫秒以内,只要不是频繁发生,这点停顿是可以接受的。所以,Serial收集器对于运行在来说是一个很好的选择。
Serial收集器 运行示意图如下:
stw : stop-the-word 停所有工作线程; 采用复制算法;
注: 这一收集器,随着内存越来越大,执行效率太慢了.好比一个人最开始住的是5平米的房子,打扫房子很快,当住上500平大房子的时候,清理一遍房子花的时间太长,这种单线程就没法使用了 后面就有了多线程的版本- ParNew收集器
2.2 Serial Old收集器
Serial Old 和Serial 相比就是应用在老年代的垃圾收集器,也是单线程,但是算法不是copy,而是Mark-Compact 标记整理算法,也是stw:暂停所有线程进行垃圾回收;
所以 Serial 和Serial Old 组合使用,可用内存一般不大(几十M至一两百M)的服务器环境中,不适合当前的大内存了
2.3ParNew收集器
ParNew垃圾收集器是Serial收集器的改进多线程版本(因为内存的不断增大),除了多线程外,其余的行为、特点和Serial收集器一样,实现算法跟Serial完全一样(copy算法),也是stw下执行;
但是如果CPU数量为1个或者少于4个时,该种收集器的性能并不会比Serial要好。因为除去上下文切换,以及占用用户线程CPU时间片,导致用户线程被拖慢
在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;CMS是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;
1 ) CMS作为老年代收集器,但却无法与JDK1.4已经存在的新生代收集器Parallel Scavenge配合工作;
2) 因为Parallel Scavenge(以及G1)都没有使用传统的GC收集器代码框架,而另外独立实现;而其余几种收集器则共用了部分的框架代码;
设置参数
“-XX:+UseConcMarkSweepGC”:指定使用CMS后,会默认使用ParNew作为新生代收集器; “-XX:+UseParNewGC”:强制指定使用ParNew; “-XX:ParallelGCThreads”:指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量
2.4.Parallel Old
这个是Serial Old的多线程版本,应用在老年代的收集器,也是标记-整理算法,并且是stw的执行收集。但是如果CPU数量少的话性能一样不好。但是现在无论是PC还是server CPU数量都不再是性能瓶颈限制了,所以目前它跟Parallel Scavenge的配合是吞吐量优先场景的优先收集器选择。
2.5Parallel Scavenge
一种新生代垃圾收集器,与 ParNew相比不可以与cms一起组合使用,PS也是复制算法,它与前两种收集器最大的区别是,它关注的是吞吐量而不是延迟。也被称为是吞吐量优先的收集器。其中,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
主要使用场景:主要适合在后台运算而不是太多交互的任务,高吞吐量则可以最高效率的利用CPU时间,尽快的完成程序的运算任务。当然,如果想要降低停顿时间,相应的也会影响吞吐量
2.6 CMS
CMS,Concurrent Mark Sweep,这是一款真正的并发收集器,就是在线程执行过程中也可以进行垃圾收集的收集器,在一些对响应时间有很高要求的应用或网站中,用户程序不能有长时间的停顿,CMS 可以用于此场景。 分为四个过程 ,1初始标记 ,2并发标记,3重新标记,4并发清理
CMS执行流程图
CMS采用了多种方式尽可能降低GC的暂停时间,减少用户程序停顿。停顿时间降低的同时牺牲了CPU吞吐量 。因为并发情况占用大量cpu资源这是在停顿时间和性能间做出的取舍,可以简单理解为"空间(性能)"换时间CMS 是一个承前启后的收集器下面是不分代收集器
2.7 G1(Garbage First)
在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术。同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷G1收集器,是比前面的更优秀,真正有突破的一款垃圾收集器。其实在G1中还是保留了分代的概念,但是实际上已经在新生代和老年代中没有物理隔离了。在G1中,内存空间被分割成一个个的Region区,所谓新生代和老年代,都是由一个个region组成的。同时G1也不需要跟别的收集器一起配合使用,自己就可以搞定所有内存区域。整体上来讲不是一个分代收集器,是一个通吃收集器。这也是JVM内存管理和垃圾收集的一个发展趋势。从后面zgc中我们可以更清晰的看到这个变化。
G1采用了标记-整理算法,避免了CMS中的内存碎片问题,另外它能达到可控的垃圾时间。是一款优秀的收集器。即便如此,从2004年第一篇论文发表到真正商用推出,也是到了jdk1.7。实现上并不是那么容易的。
G1的工作过程:
初始标记:这个过程跟CMS第一个过程差不多,只是标记一下GC Root关联的对象。
并发标记:这个过程时间比较久,分析GC Root到所有对象的可达性分析。如果从GC Root节点开始遍历所有对象会比较耗时,实际上JVM也不是这么做的。JVM是使用Remembered Set保存了对象引用的调用信息,在可达性分析的时候只需要同时遍历remembered set就好了,不需要从根节点开始挨个遍历。
最终标记:由于并发标记阶段,用户线程仍然在工作,会对标记产生一些偏差,这时候需要通过remembered set log来记录这些改变,在这个阶段将改变合并到remembered set中。完成最终标记。
筛选清除:通过标记整理的算法,根据用户配置的回收时间,和维护的优先级列表,优先收集价值最大的region。收集阶段是基于标记-整理和复制算法实现
2.8 ZGC
zgc是jdk11中要发布的最新垃圾收集器。完全没有分代的概念,先说下它的优点吧,官方给出的是无碎片,时间可控,超大堆。
2.9 Shenandoah
Shenandoah是一款concurrent及parallel的垃圾收集器;跟ZGC一样也是面向low-pause-time的垃圾收集器,不过ZGC是基于colored pointers来实现,而Shenandoah GC是基于brooks pointers来实现。
其实低停顿的GC,业界早就出现,只不过Java比较晚Azul的Zing中C4 GC 土豪选择oracle中的HotSpot ZGC JDK11的选择R大说ZGC说抄袭Azul的,两者是等价的。
2.10 Epsilon
java 11 新的Epsilon垃圾收集器
Epsilon(A No-Op Garbage Collector)垃圾回收器控制内存分配,但是不执行任何垃圾回收工作。一旦java的堆被耗尽,jvm就直接关闭。设计的目的是提供一个完全消极的GC实现,分配有限的内存分配,最大限度降低消费内存占用量和内存吞吐时的延迟时间。一个好的实现是隔离代码变化,不影响其他GC,最小限度的改变其他的JVM代码。
参考一下
最后附一张各个收集器的组合流程图