3. 垃圾收集器 和 内存分配策略

1.GC垃圾回收机制

引用计数:在进行垃圾回收的时候首先要判断对象是否存活,一种方法是采用引用计数法,但是主流的Java虚拟机并不是采用这种方法,因为引用计数方法无法解决对象相互循环引用的问题。

可达性分析:在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。

3. 垃圾收集器 和 内存分配策略

 

那么那些点可以作为GC Roots呢?一般来说,如下情况的对象可以作为GC Roots:

    1. 虚拟机栈(栈桢中的本地变量表)中的引用的对象
    2. 方法区中的类静态属性引用的对象
    3. 方法区中的常量引用的对象
    4. 本地方法栈中JNI(Native方法)的引用的对象

 

2. Java中的引用:

https://www.jianshu.com/p/147793693edc

 

3.finalize方法:

public class FinalizeEscapeGC {

public static FinalizeEscapeGC SAVE_HOME = null;

 

public void isAlive() {

System.out.println("Yes 我活着");

}

 

//垃圾回收准备释放内存时,会先调用该方法,该方法

@Override

protected void finalize() throws Throwable {

super.finalize();

System.out.println("finalize method executed");

//加上了一个强引用

FinalizeEscapeGC.SAVE_HOME = this;

}

 

public static void main(String[] args) throws InterruptedException {

SAVE_HOME = new FinalizeEscapeGC();

 

//对象第一次尝试拯救自己

SAVE_HOME = null;

System.gc();

Thread.sleep(500);

if (SAVE_HOME != null) {

SAVE_HOME.isAlive();

} else {

System.out.println("被回收了 -------");

}

 

 

//对象第二次尝试拯救自己

SAVE_HOME = null;

System.gc();

Thread.sleep(500);

if (SAVE_HOME != null) {

SAVE_HOME.isAlive();

} else {

System.out.println("被回收了 -------");

}

}

}

Yes 我活着

被回收了 -------

 

4.回收方法区:

无用的类判断标准:

  • 该类的所有实例已经被回收
  • 加载该类的ClassLoad已经被回收
  • 该类对应的java.long.Class没有在任何地方被引用,无法在任何地方通过反射访问该方法。

 

5.垃圾收集算法:

a.标记清除法:效率:标记过程,清除过程效率不高

空间问题:清除后产生大量内存碎片

b.复制算法:为了解决效率问题,其将可用内存分块,每次使用一块,用完之后,将活着的对象复制到这块上,通过这样的操作,对其进行集中处理,之后一次性清理掉。

c.标记-整理算法:标记--所有存活对象向一端移动,清理掉端边界以外的内存。

d.分代收集算法:分为新生代,老生代,新生代存活低,使用复制算法,老生代,存货高,采用标记-整理算法。

 

6.通过使用GC安全点和安全区域, 支持准确根集合枚举。

https://blog.csdn.net/Michael__Jack/article/details/71249353

 

7.垃圾收集器:https://crowhawk.github.io/2017/08/15/jvm_3/

3. 垃圾收集器 和 内存分配策略

Serial:单线程-复制算法

  缺点:单线程,执行时会把所有东西都停掉。

  优点:简单高效

ParNew:复制

  Serial的多线程版本

Parallel Scavenge:吞吐量优先收集器,设置了一个自适应调整策略的开关。

  目标,达到一个可控制的吞吐量(吞吐量 = 运行用户代码的时间 /(运行用户代码的时间+ 垃圾收集时间),通过设置最大垃圾停顿时间和设置吞吐量大小来控制)

Serial Old:单线程-标记整理

Parallel Old:标记整理

CMS:以获取最短停顿时间为目的。标记-清除算法。并发收集-低停顿

过程:

  初始标记:stop thee world,标记GC Root能直接关联的对象

  ->并发标记:GC RootTracing 过程

  ->重新标记:stop thee world 修改因为并发,导致标记变动

  ->并发清除:可以与用户线程一起工作

G1:并行,并发-分代收集-空间整合-可预测停顿---------标记-整理--有规律的避免在整个Java堆中进行全区域的收集。

将Java堆划多个大小相等的独立区域(Region),跟踪各个Region垃圾堆积的价值大小,后台维护一个优先列表。

 

8.GC日志分析:https://blog.csdn.net/TimHeath/article/details/53053106

    • GC日志开头的”[GC”和”[Full GC”说明了这次垃圾收集的停顿类型,如果有”Full”,说明这次GC发生了”Stop-The-World”。因为是调用了System.gc()方法触发的收集,所以会显示”[Full GC (System.gc())”,不然是没有后面的(System.gc())的。
    • “[PSYoungGen”和”[ParOldGen”是指GC发生的区域,分别代表使用Parallel Scavenge垃圾收集器的新生代和使用Parallel old垃圾收集器的老生代。为什么是这两个垃圾收集器组合呢?因为我的jvm开启的模式是Server,而Server模式的默认垃圾收集器组合便是这个,在命令行输入java -version就可以看到自己的jvm默认开启模式。还有一种是client模式,默认组合是Serial收集器和Serial Old收集器组合。
    • 在方括号中”PSYoungGen:”后面的”3686K->664K(38400K)”代表的是”GC前该内存区域已使用的容量->GC后该内存区域已使用的容量(该内存区域总容量)”
    • 在方括号之外的”3686K->672K(125952K)”代表的是”GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)”
    • 再往后的”0.0016607 sec”代表该内存区域GC所占用的时间,单位是秒。
    • 再后面的”[Times: user=0.00 sys=0.00, real=0.00 secs]”,user代表进程在用户态消耗的CPU时间,sys代表代表进程在内核态消耗的CPU时间、real代表程序从开始到结束所用的时钟时间。这个时间包括其他进程使用的时间片和进程阻塞的时间(比如等待 I/O 完成)。
    • 至于后面的”eden”代表的是Eden空间,还有”from”和”to”代表的是Survivor空间。

 

9.常用的内存分配策略:(Minor GC:新生代GC Full GC:老年代GC)

  • 对象优先在Eden分配。(先在新生代Eden区域分配,不足的话,将部分新生代转移到老生代)
  • 大对象直接进入老生代。(需要大量连续内存空间的对象)(尽量避免,经常出现大量“短命大对象,这样会造成内存还有足够空间,但是触发GC去获得连续空间来安置他们”)
  • 长期存活对象将进入老年代。(维护一个Age计数器,记录对象存活过的GC次数,一般存活一次之后将其从Eden移入Survivor中,之后,到了15将其放入老年代中)
  • 动态对象年龄判定。(在Survivor中,相同年龄对象大小综合大于Survivor空间的一半,年龄大于或者等于该对象就可以直接进入老年代)
  • 空间分配担保。(Minor GC之间,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,成立,那么Minor GC可以确保是安全的。如果不成立,查看是否允许担保失败,允许的话,检查老年代最大可用连续空间是否大于历次晋升的平均大小,大于,进行一次Minor GC,不允许,取出前一次回收的数值作为经验值,与老年代剩余空间比较,决定是否进行Full GC回收)