垃圾收集就是我们熟知的GC(Carbage Collection),为了达到更高的并发量,我们需要对这些自动化技术实施监控与调节。
新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
Gc触发时机
https://www.cnblogs.com/williamjie/p/9516367.html
堆里几乎存有所有对象实例。垃圾回收对象前,首先需要判断对象是否还存活,这里有两种方法,引用计数算法和根搜索算法(可达性分析)。
推荐对象分配的地方的博客
https://blog.csdn.net/zhaohong_bo/article/details/89419480
在对象中添加一个引用计数器,每当有一个地方引用它是加一,引用失效则减一。任何时刻引用计数都为零的对象,不可能再被使用。
优点:简单高效
缺点:很难解决循环引用问题
java你要使用这种方式管理内存。
根搜索算法基本思路:通过一系列名为"GC Roots”的对象为起始点,从这些节点向下搜索,经过路线就是引用链,当一个对象和GC Roots之间没有引用链,即不可达时,证明对象是不可用的。
可作为GC Roots的对象:
JDK1.2之前,引用就是reference类型的数据中储存着一块内存的起始地址,就是引用。只有是或者不是,非常狭隘。
JDK1.2之后,对引用的概念进行扩充,出现了我们熟悉的四种引用,强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom),强度依次递减。
在可达性分析中,某个对象不可达,不是就立即死亡,而是在缓刑阶段。宣告一个对象死亡,至少要经过两次标记过程:在可达性分析中,对象不可达,会被第一次标记并进行一次筛选,筛选条件是对象是否有必要执行finalize()方法。
没有必要执行的两种情况:
(1)对象没有覆盖finalize()方法
(2)finalize()方法已经被虚拟机调用了
如果对象被判定有必要执行finalize()方法,那么它会被放置在一个 名字为 F-Queue的队列中,然后有一条虚拟机自动建立的,低优先级的Finalizer线程去执行队列里的对象的finalize() 方法,但是不承诺会等到它方法执行结束。
不承诺等到对象finalize()方法执行完的原因:
如果一个对象的finalize方法执行缓慢或者发生死循环,会导致内存回收系统崩溃。
finalize()方法是对象不被回收的最后一次机会:方法执行后,会对FQ队列里的对象进行一次标记-重新与引用链上的任何一个对象建立关联即可从“即将回收的集合”中移除。
注意:一个对象的finalize()方法只会被系统调用一次,对于下次GC,不会被调用
finalize方法被说适合用来关闭外部资源,但是使用Try-finally或者其他方式会更好,尽量避免使用它。
方法区,在HotSpot虚拟机里也叫永久代。方法区的回收效率远低于堆的回收效率。规范里可以不要求虚拟机实现方法区的垃圾回收。
方法区的垃圾收集主要包括 废弃常量 和 无用的类信息 两部分。回收无用常量的方式和回收堆里的对象的方法十分类似,通过引用判断。
而判断无用类,则条件更苛刻一点。
最基本的收集算法。分为两个阶段,标记和回收。首先先标记出所有将要回收的对象,标记完成后统一回收掉所有标记的对象。
主要缺点:
标记和清除两个过程效率不高。
会产生内存碎片。
将可用内存分为大小相等的两块,每次只使用一块。当这一块内存用完了,就将活着的对象复制到另一块上面,然后把已经使用过的那块内存一次清理。
实现简单,运行高效。只是浪费空间。
这种算法被运用到回收新生代。新生代被划分为eden区和两块Survivor区。
每次使用eden区和一块survivor区,另外一块survivor区供复制使用。
eden区survivor区比例 8:1(一共占新生代的90%)。当survivor区不够用时,就会使用其他内存区域(老年代)进行分配与担保。
复制收集算法需要进行较多的复制操作,效率会变低,而且浪费50%的内存空间,而且需要有额外的分配担保区域来应对100%对象存活的情况。所以老年代一般不采用这种算法。
老年代的特点是对象存活时间相对较长,针对这种特点,有人提出一种“标记-整理算法”。先标记,所有存活对象再向一端移动,然后清理边界以外的内存。
现代商业虚拟机都采用分代收集算法。根据对象存活周期不同将内存分为几块,一般是新生代和老年代。根据各年代的特点,采用最适合的收集算法。
新生代每次收集时都有大批对象死去,采用复制算法。老年代对象存活率高,采用标记-整理或者标记-清除算法。
对于内存回收,收集算法是方法论,垃圾收集器是具体实现。
java虚拟机规范中对垃圾收集器应该如何实现没有做如何规定,因此不同厂商,不同版本的虚拟机提供的垃圾收集器可能不同。一般会提供参数,让用户根据自己应用的特点和要求组合出各年代所使用的收集器。
例如Sun HotSpot虚拟机包含的所有收集器,连线表示可以搭配使用。
没有完美的收集器,选择合适的最重要。
Serial收集器是最基本,历史最悠久的收集器,在JDK1.3之前,新生代收集的唯一选择。
它是单线程收集器,每次收集,都会停止其他所有线程(Stop the World),直到收集结束。
Stop the World 使得用户体验不好,但是简单高效(与其他收集器的单线程相比),对于单核环境,没有线程交互开销。
ParNew收集器是Serial收集器的多线程版本,仍然会 Stop the World,j即并行,没有做到并发。
它是许多运行在 Server模式下的虚拟机 新生代收集器的首选。
因为除了Serial ,只有它能够和CMS收集器搭配使用。
并行(Parallel):多条回收线程并行工作,用户线程停止等待。
并发(Concurrent):用户线程和回收线程同时执行(并行或者交替执行)。
Parallel Scavenge收集器是一个新生代收集器。它使用复制算法而且并行。
看上去和Parallel New一样。但是 Parallel Scavenge收集器的关注点与其他收集器关注点不同。CMS收集器等关注的是,垃圾收集时尽可能减少用户线程的停顿时间。Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。
吞吐量:CPU用于运行用户代码的时间与CPU总耗时的比值。
那么停顿时间与吞吐量怎么选择呢?
停顿时间越短越适合需要与用户交互的程序,以良好的响应速度提升用户体验。
高吞吐量则可以最高效率利用CPU,尽快完成程序运算速度,适合后台运算,不需要太多交互的任务。
Parallel Scavenge收集器提供 -XX:MaxGCPauseMillis参数控制停顿时间,
-XX:GCTimeRatio参数控制吞吐量大小。其他参数可以查阅资料。
Serial Old是Serial收集器的老年代版本,用来回收老年代的。单线程,使用 标记-整理算法。
它被Clinet模式下的虚拟机使用。
在Server模式下,在JDK1.4及之前,与Parallel Scavenge收集器搭配使用。
以及作为CMS收集器的预备方案,在并发收集遇到 Concurrent Mode Failure时使用。
Parallel Scavenge收集器的老年代的版本,也是用来回收老年代。
多线程,使用标记-整理算法。
这个收集器是JDK1.6之后才提供的。
由于这个收集器的出现,“吞吐量优先”才名副其实。Parallel Scavege +Parallel Old 的组合。在Parallel Old 收集器出现之前,Parallel Scavege收集器位置比较尴尬,不能与CMS收集器搭配使用,而其他老年代收集器会影响Parallel Scavege 收集器的 吞吐量。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短停顿时间为目标的收集器,重视服务的响应速度,所以网站,B/S系统服务端等这类场景,是适合CMS的。
CMS是基于:“标记-清除”算法实现的。实现过程相对复杂一些。
包括
初始标记和重新标记两个过程仍需要暂停所有用户线程(Stop the World)。
整个过程 并发标
记和并发清除的耗时最长。
该收集器的优点:低停顿,并发收集。
缺点:
(1)会占用一部分线程,导致应用变慢,吞吐量降低。
默认回收线程数 (CPU数+3)/4,所以CPU数量地域4个时,对用户程序影响大。
(2)无法处理浮动垃圾,可能出现 Comcurrent Mode Failure。在并发收集阶段,用户线程还在运行,产生的垃圾只有下次GC才能被回收。因为在一些阶段用户线程和并发线程是并行的,需要预留内存给用户线程。而CMS运行期间,这个预留内存不足时,就导致 Concurrent Mode Failure ,启动预备方案,Parallel Old 重新回收老年代,导致停顿时间变长。这个只能调整参数,减少这种情况的出现。
(3)由于采用 标记-清除算法,会尝试大量内存碎片。没有新生代没有足够连续内存时,就会考虑老年代的内存空间,但是如果还是无法找到足够大的内存空间,就会提前出发 Full GC。当然,CMS通过设置相应参数提供空间整理功能(无法并发),以及多少次FullGC后,进行空间整理的参数。
G1收集器是垃圾收集器理论的进一步发展的产物。
与CMS收集器相比:
(1)采用 标记-整理算法,不会产生内存碎片。
(2)可以非常精确的控制停顿时间,使用户可以设置一次M毫秒的时间内,消耗在垃圾回收的时间不超过 N毫秒。
G1收集器可以实现基本不牺牲吞吐量的情况下,完成低停顿的垃圾回收。因为它极力回避全区域的垃圾收集。之前的收集器的回收范围都是整个新生代或者老年代。
G1收集器将整个java堆(包括新生代,老年代)分为多个大小固定的独立区域(Region),跟踪这些区域的垃圾堆积程度,后台维护一个优先列表,每次根据允许的回收时间,优先回收垃圾最多得区域(Carbage First)。这样的优先级,保证了G1收集器在有限时间内的最高收集效率。
自动内存管理解决两个问题:对象内存分配 和 回收对象内存。
下面是普遍几条分配规则。