《深入理解JAVA虚拟机》3.垃圾收集器与内存分配策略

女爷i 2023-03-13 11:34 60阅读 0赞
  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

一、对象已死

1.引用计数算法

  1. 为对象添加一个引用计数器,一个地方引用,计数器加1,引用失效则减1,当计数器为0是,该对象将不再使用。该算法存在孤岛问题(循环依赖)。如下图,A对象与B对象,计数器均为1,但是已经废弃,通过该算法无法回收。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xhb3hpbGFveGlf_size_16_color_FFFFFF_t_70

2.可达性分析算法

  1. GC Roots出发,向下搜索,所有走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有人格引用链可到达,证明此对象不可用。下图中的Obj5Obj6Obj7均不可用。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xhb3hpbGFveGlf_size_16_color_FFFFFF_t_70 1

GC Roots的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中静态类属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(Native方法)引用的对象。

引用:

  • 强引用(Strong Reference):垃圾收集器不会回收被引用的对象。Object o = new Object();
  • 软引用(Soft Reference):软引用关联的对象在系统将要发生内存溢出异常之前会将这些对象列进回收范围进行 二次回收。SoftReference类实现。
  • 弱引用(Weak Reference):被弱引用关联的对象,在下次垃圾收集发生时,无论内存是否足够,都将回收。WeakReference类实现。
  • 虚引用(Phantom Reference):在对象回收时收到一个系统通知。PhantomReference类实现。

finalize():

  1. 对象在可达性分析后,发现内有与GC Roots相连接的引用链,那么将第一次标记,并再进行一次筛选:也就是有没有必要之心finalize(),如果对象没有覆盖finalize()或者已经执行过一次,则不再执行。如果判定需要执行,则将该对象放入F-Queue队列中,由虚拟机自动创建优先级低的Finalizer线程执行。执行后再进行一次标记,如果对象重新与引用链上的对象建立关联,则该对象将被移出“即将回收”对象集合。

方法区回收:

  • 该类的所有实例都已经回收。
  • 加载该类的ClassLoader已经被回收。
  • 该类的java.lang.Class对象没有任何地方被引用。

二、垃圾收集算法

1.标记-清除算法(Mark-Sweep)

  1. 首先标记出所有需要回收的对象,标记完成后统一回收标记对象。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xhb3hpbGFveGlf_size_16_color_FFFFFF_t_70 2

2.复制算法(Copying)

  1. 将内存划分为2块,每次只是用其中一块,当这一块使用完后,将存活的对象复制到另外一块,然后对原来那块内存空间清理。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xhb3hpbGFveGlf_size_16_color_FFFFFF_t_70 3

3.标记-整理算法

  1. 首先标记出所有需要回收的对象,然后将所有存活对象像一端移动,清理掉端边界以外的内存。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xhb3hpbGFveGlf_size_16_color_FFFFFF_t_70 4

4.分代收集算法

  1. 按照对象的存活周期进行内存划分,一般分为新生代与老年代,然后再对不同的分代使用不同的回收算法。

20200513090657337.png

三、垃圾收集器

1.Serial收集器:

  1. 新生代收集器、单线程、复制算法

2.ParNew收集器

  1. 新生代收集器、多线程、复制算法

3.Parallel Scavenge收集器

  1. 新生代收集器、多线程、复制算法、可控制的吞吐量(cpu用户时间/cpu用户时间+垃圾回收时间)
  • -XX:MaxGCPauseMillis 最大停顿时间,大于0的毫秒数
  • -XX:GCTimeRatio 吞吐量,0到100的整数
  • -XX:+UseAdaptiveSizePolicy 不需要执行新生代大小,Eden与Survivor的比例,Jvm自动调整

4.Serial Old收集器

  1. 老年代收集器、单线程、标记-整理算法

5.Parallel Old收集器

  1. 老年代收集器、多线程、标记-整理算法

6.CMS收集器

  1. 老年代收集器、多线程、标记-清除算法、最少停顿时间为目标
  • 初始标记:STW,标记GC Roots直接关联的对象。
  • 并发标记:可与用户线程并行,通过初始标记的对象进行GC Roots Tracing。
  • 重新标记:STW,修正并发标记期间因程序的运行产生的标记变动。
  • 并发清除:可与用户线程并行,清理内存。

    1. CMS缺点
  • CPU资源敏感

  • 无法处理浮动垃圾,会出现Concurrent Mode Failure,导致Full GC。-XX:CMSInitiatingOccupancyFraction 触发回收的内存比例,默认68%
  • 内存碎片化(标记-清除算法的弊端),需要依赖Full GC进行内存整理。-XX:+UseCMSCompactAtFullCollection 在要进行FullGC时开启内存碎片整理,默认开启。-XX:CMSFullGCsBeforeCompaction 执行多少次FullGC后压缩一次内存,默认0。

7.G1收集器

  1. 特点:并行与并发、分代收集、空间整合、可预测停顿。
  2. 建立多个大小相等的Region区域,然后判断回收价值,设定回收优先级。每个Region不是独立的,因为存在对象引用。为避免GC时对所有Region扫描,使用了Remembered Set记录。如下图,Region1A对象引用Region2中的B对象,则Region2会在B建立引用时,在自己的Remembered Set中记录下Region1,然后Region2GC时,就会带上Region1一起扫描。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xhb3hpbGFveGlf_size_16_color_FFFFFF_t_70 3

四、GC日志

//GC日志截取

16.796: [GC (Allocation Failure) [PSYoungGen: 148607K->8216K(147968K)] 177131K->39365K(235520K), 0.0237545 secs] [Times: user=0.09 sys=0.00, real=0.03 secs]

17.036: [GC (Metadata GC Threshold) [PSYoungGen: 70383K->4690K(155648K)] 101532K->39199K(243200K), 0.0125718 secs] [Times: user=0.04 sys=0.00, real=0.02 secs]

17.049: [Full GC (Metadata GC Threshold) [PSYoungGen: 4690K->0K(155648K)] [ParOldGen: 34508K->36201K(118784K)] 39199K->36201K(274432K), [Metaspace: 56798K->56798K(1099776K)], 0.3047091 secs] [Times: user=1.05 sys=0.01, real=0.30 secs]

  • 16.796、17.036、17.049:代表GC发生的时间,JVM启动后经历的秒数。
  • [GC、[Full GC:垃圾收集的停顿类型。
  • Allocation Failure、Metadata GC Threshold:发生GC的原因,Allocation Failure新生代分配内存失败,Metadata GC Threshold有元空间GC导致的FullGC
  • [PSYoungGen、[ParOldGen、[Metaspace:GC发生的区域。
  • 148607K->8216K(147968K):GC前内存->GC后内存(该区域总容量)
  • user=0.09 sys=0.00, real=0.03:CPU用户态好使0.09s,内核态耗时0.00s,从开始到结束所经过的时钟时间为0.03s(多核的CPU的user或者sys会超过real)

五、内存分配与回收策略

  1. 对象优先在Eden分配,大对象直接进入老年代,长期存活的对象将进入老年代、动态年龄判定
  • 年轻代分为Eden与2个Survivor区,其中-Xmn设置新生代大小,-XX:SurvivorRatio设置Eden与Survivor的比例,默认是8,也就是8:1:1。
  • -XX:PretenureSizeThreshold设置大对象size阈值,超过该值直接进入老年代,单位B。
  • -XX:MaxTenuringThreshold设置对象年龄阈值,超过该年龄的对象晋升到老年代,默认15。每次Minor GC,年龄加1。
  • Survivor中相同年龄的所有对象大小总和大于Survivor的一半,那么大于等于该年龄的对象进入老年代。
  • 空间分配担保,如下图

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xhb3hpbGFveGlf_size_16_color_FFFFFF_t_70 5

发表评论

表情:
评论列表 (有 0 条评论,60人围观)

还没有评论,来说两句吧...

相关阅读