如何判断对象是否需要回收
- 引用计数算法:有引用就+1,引用失效就-1, 这种算法实现简单,判断效率也高,但是它很难解决对象之间的互相引用问题,A引用B,B引用A,但是A、B都没有被使用了,这种算法回收不了。
- 可达性分析算法:通过一系列的GC ROOTS作为起始点,从这些起始点向下搜索,当一个对象不可达时证明此对象不可用,GC ROOTS对象的种类:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用、本地方法栈中引用对象
不管什么算法都跟引用有关,Java里面有下面四种引用由强到弱:
- 强引用:代码中普遍存在,类似Object obj = new Object(); 垃圾回收肯定不会回收
- 软引用:对象还有用,但又不是必须的,系统将要发生内存溢出时回收,SoftReference类实现软引用
- 弱引用:也是非必须的对象,垃圾收集器下次工作会回收,WeakReference类来实现弱引用
- 虚引用:最弱的一种关系,完全不影响对象的生存时间,也无法通过虚引用来取得一个对象实例,为一个对象设置虚引用的唯一目的就是能在这个对象被回收时收到一个系统通知。
收集算法
- 标记-清除:先标记出需要回收的对象,统一回收被标记的对象,容易产生内存碎片,效率也不高
- 复制:为了解决效率问题,把内存分开, 只是用其中的一部分,垃圾回收的时候把存活下的对象负责到另一部分,然后把使用过的空间一次性清理掉, 年轻代98%的对象都是需要回收的,所以内存分配的时候不需要1:1,而是分成eden区和两块存活区,每次把eden和一块存活区负责到另一块存活区,HotSpot虚拟机推荐用8:1,所以一块存活取空闲着只会浪费10%,当然如果存活的对象多于这10%,将直接进入老年代。
- 标记整理:年老代存活对象比例较大,如果用复制算法就会进行较多的复制操作,效率低下,并且复制算法需要浪费一部分空间,所以这种算法不适合年老代,提出另外一种算法标记整理,标记部分跟标记清除算法一样,整理阶段是让存活的对象都向一端移动,然后直接清理掉边界以外的内存。
- 分代收集算法:根据对象存活周期不同将内存分几块,根据每块的特点使用不同的收集算法
垃圾回收器
内存回收的具体实现,用一些线程模型结合上面算法实现了多种垃圾回收器,如下图:
存在连线可以搭配使用,目前没有万能的回收器,只能组合使用
- 串行收集serial:工作线程都停下来,一个线程去完成垃圾回收,复制算法
- serial Old: 同上,年老代使用标记-整理算法
- 并行收集ParNew: serial收集器的多线程版本,其他参数配置回收策略都与serial一样,看上面的连线只有ParNew收集器和serial收集器能跟CMS合用
- Parallel Scavenge: 采用复制算法,也是并行收集,它更关注如何控制吞吐量,吞吐量=运行用户代码时间/总时间,它更适合后台运算的程序。
- Parallel Old: Parallel Scavenge老年代版本,采用多线程和标记整理算法
- CMS:CMS偏响应时间Parallel偏吞吐量,过程,
初始标记(要停用户线程):标记一下GC Roots能直接关联到的对象
并发标记:进行GC Roots Tracing的过程 重新标记(停用户线程),修正并发标记期间用户线程继续运作而导致标记产生变动的那一部分对象的标记记录 并发清除:基于标记清除算法做的,所以这阶段就是清除, 耗时最长的是可以与用户线程一起运行的并发标记和并发清除阶段。
- G1:它的目标是作为一款服务器的垃圾收集器,因此,它在吞吐量和停顿控制上,预期要优于 CMS 收集器。与 CMS 收集器相比,G1 收集器是基于标记-压缩算法的(使用G1后内存空间不是连续的,由一些Region组成,通过预测要回收的Region来进行部分回收,2-8理论)。因此,它不会产生空间碎片,也没有必要在收集完成后,进行一次独占式的碎片整理工作。G1 收集器还可以进行非常精确的停顿控制。它可以让开发人员指定当停顿时长为 M 时,垃圾回收时间不超过 N。
这里的并行、并发的概念:
并行(parallel):多个线程同时工作,但是用户线程任然处于等待状态 并发(Concurrent):用户线程与垃圾收集器线程同时执行。理解GC日志
[GC、[Full GC表示停顿类型,[DefNew、[Tenured、[Perm是发生区域,这里的区域名称与使用的垃圾收集器密切相关,方括号里面的3324K->152K(3712K)表示GC之前该内存已使用容量->GC之后该内存使用容量(该内存区域总容量),方括号外面的3324K-152K(11904K)表示GC之前Java堆已使用容量->GC之后Java堆使用容量(Java堆总容量),最后0.0025925secs表示该内存区域GC使用时间。
垃圾收集器参数
-XX:+<option> 启用选项
-XX:-<option> 不启用选项 -XX:<option>=<number> -XX:<option>=<string>-XX:+UseSerialGC | Jvm运行在Client模式下的默认值,打开此开关后,使用Serial + Serial Old的收集器组合进行内存回收 |
-XX:+UseParNewGC | 打开此开关后,使用ParNew + Serial Old的收集器进行垃圾回收 |
-XX:+UseConcMarkSweepGC | 使用ParNew + CMS + Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用。 |
-XX:+UseParallelGC | Jvm运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old的收集器组合进行回收 |
-XX:+UseParallelOldGC | 使用Parallel Scavenge + Parallel Old的收集器组合进行回收 |
-XX:SurvivorRatio | 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Subrvivor = 8:1 |
-XX:PretenureSizeThreshold | 直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
-XX:MaxTenuringThreshold | 晋升到老年代的对象年龄,每次Minor GC之后,年龄就加1,当超过这个参数的值时进入老年代 |
-XX:UseAdaptiveSizePolicy | 动态调整java堆中各个区域的大小以及进入老年代的年龄 |
-XX:+HandlePromotionFailure | 是否允许新生代收集担保,进行一次minor gc后, 另一块Survivor空间不足时,将直接会在老年代中保留 |
-XX:ParallelGCThreads | 设置并行GC进行内存回收的线程数 |
-XX:GCTimeRatio | GC时间占总时间的比列,默认值为99,即允许1%的GC时间,仅在使用Parallel Scavenge 收集器时有效 |
-XX:MaxGCPauseMillis | 设置GC的最大停顿时间,在Parallel Scavenge 收集器下有效 |
-XX:CMSInitiatingOccupancyFraction | 设置CMS收集器在老年代空间被使用多少后出发垃圾收集,默认值为68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70 |
-XX:+UseCMSCompactAtFullCollection | 由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效 |
-XX:+CMSFullGCBeforeCompaction | 设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用 |
-XX:+UseFastAccessorMethods | 原始类型优化 |
-XX:+DisableExplicitGC | 是否关闭手动System.gc |
-XX:+CMSParallelRemarkEnabled | 降低标记停顿 |
-XX:LargePageSizeInBytes | 内存页的大小不可设置过大,会影响Perm的大小,-XX:LargePageSizeInBytes=128m |
-XX:+PrintFlagsFinal | 可查看jdk参数的默认值 |