前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【GC系列】JVM堆内存分代模型及常见的垃圾回收器

【GC系列】JVM堆内存分代模型及常见的垃圾回收器

作者头像
行百里er
发布2020-12-02 15:36:23
9150
发布2020-12-02 15:36:23
举报
文章被收录于专栏:JavaJourneyJavaJourney

1. 内存分代模型

为什么要说JVM的内存分代模型呢,因为内存分代和垃圾回收器的运行是有关系的。

现在大部分用到的垃圾回收器在逻辑上是分代的,除了G1之外的其他垃圾回收器在逻辑上和物理上都是分代的。

  • 除了Epsilon、ZGC、Shenandoah之外的GC都是是逻辑分代模型
  • G1是逻辑分代,物理上不分代
  • 除此之外的不仅逻辑分代,而且物理分代

逻辑分代是给内存做一些概念上的区分,物理分代是真正的物理内存。

具体划分

新生代(young)和老年代(old/tenured)。

新生代:刚new出来的那些对象

老年代:垃圾回收了很多次都没有把它回收掉的老对象

新生代又分为:

  • eden 默认比例是8。新new出来的对象放在eden区。
  • survivor(s1) 默认比例是1。垃圾回收一次之后跑到这个区域,该区域存放的对象不同,采取的垃圾回收算法也不同。
  • survivor(s2) 默认比例是1。

新生代存活的对象较少,使用的垃圾回收算法是拷贝算法(Copying)。

老年代活着的对象较多,垃圾回收算法适用标记压缩(Mark Compact)或者标记清除(Mark Sweep)。

几个GC的概念

  • MinorGC/YGC 新生代空间耗尽时触发的垃圾回收。
  • MajorGC/FullGC 在老年代无法继续分配空间时触发,新生代、老年代同时进行垃圾回收。

分代空间参数配置

-Xms-Xmx

-Xmn

X是非标参数,m是memory内存,s是最小值,x是最大值,n是new

分代空间大小参数配置

2. 一个对象的生命历程-从出生到消亡

一个对象被new出来之后,首先尝试进行栈上分配,栈上如果分配不下才会进入eden区;

eden区经过一次垃圾回收之后进入一个survivor区-s1区;

survivor区(s1)经过一次垃圾回收之后又进入另一个survivor区-s2区,同时eden区的某些对象也会跟着进入s2;

当对象年龄到某一个值后,会进入到old区。这个值可以通过参数:

-XX:MaxTenuringThreshold

进行配置。

下面这个图能够帮助我们了解JVM中内存分区的概念。

一个对象从出生到消亡的过程

3. 对象如何进行栈上分配

C语言中的struct结构体就可以直接在栈上分配,在Java中也有栈上分配的理念。

在JVM中,堆是线程共享的,因此堆上的对象对于各个线程都是共享和可见的,只要持有对象的引用,就可以访问堆中存储的对象数据。虚拟机的垃圾收集系统可以回收堆中不再使用的对象,但对于垃圾收集器来说,无论筛选可回收对象,还是回收和整理内存都需要耗费时间

如果确定一个对象的作用域不会逃逸出方法之外,那可以将这个对象分配在栈上,这样,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,无须通过垃圾收集器回收,可以减小垃圾收集器的负载

JVM允许将线程私有的对象打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统性能

综上,栈上分配:

  • 线程私有小对象
  • 无逃逸

在某一段代码中使用,出了这段代码就没有其他的代码认识它。

比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。

  • 支持标量替换

用普通的属性、普通的类型代替对象就叫标量替换。

意思是说JVM允许将对象打散分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。

  • 无需调整

站上分配的对象随栈帧出栈而销毁,无需内存调整。

4. 线程本地分配

对象在栈上分配不下了,会优先进行本地分配。

线程本地分配,Thread Local Allocation Buffer,简称TLAB。

很多线程都向Eden区分配对象,分配对象的线程会进行内存空间的征用,谁抢到就算谁的。出现多线程的同步,效率就会降低,因此设计了这个TLAB机制-线程本地分配。

TLAB特征:

  • 占用Eden区的大小模式是1%,这1%的空间时线程独有,分配对象的时候先向这块空间进行分配。
  • 多线程的时候不用竞争Eden区就可以申请空间,效率提高。
  • 分配的是小对象。
  • 无需调整。

我们来测试一下,在栈上分配和TLAB是否提升了效率。

public class TLABTest {

    // -XX:-DoEscapeAnalysis 去掉逃逸分析
    // -XX:-EliminateAllocations 去掉标量替换
    // -XX:-UseTLAB 去掉TLAB
    public static void main(String[] args) {
        TLABTest t = new TLABTest();
        long start = System.currentTimeMillis();
        //执行1000万次alloc
        for (int i = 0; i < 1000_0000; i++) {
            t.alloc(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("spends " + (end - start));
    }

    // 该方法只new一个对象出来,没有任何引用指向他,
    // 除了这个方法就没人认识它,所以没有逃逸
    void alloc(int id) {
        new User(id, "name" + id);
    }

    class User {
        int id;
        String name;

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
}

执行时加上

-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB
  • -XX:-DoEscapeAnalysis 去掉逃逸分析
  • -XX:-EliminateAllocations 去掉标量替换
  • -XX:-UseTLAB 去掉TLAB

运行结果:

spends 1237

然后在去掉-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB,也就是默认情况下执行:

spends 643

效率很明显提升了!

5. 常见的垃圾回收器

5.1 Serial

当Serial工作的时候,所有正在工作的线程全部停止。

线程停止有一个safe point(安全点),需要找到一个安全点上进行线程停止,该垃圾回收器停顿时间较长(STW - Stop The World),现在用的较少。

Serial

5.2 Serial Old

用于老年代,也是STW,采用的是标记清除算法(Mark-Sweep),单线程。

5.3 Parallel Scavenge

如果JVM没有做过任何参数设定的话,默认就是Parallel ScavengeParallel Old(PS+PO)。

Parallel Scavenge和Serial的区别是PS是多线程清理垃圾。

Parallel Scavenge

5.4 Parallel Old

用于老年代,STW,多线程执行,采用标记压缩整理算法(Mark-Compact)。

5.5 ParNew

Parallel NEW的意思,就是Parallel Scavenge的新版本。它就是在PS的基础上做了一些增强一遍它能和CMS配合使用,CMS在某一个特定阶段的时候会和ParNew同时运行。

ParNew工作的时候其余线程不能工作,必须等GC结束才行。

它工作时也STW,采用的是Copying算法,多线程执行。

5.6 CMS

前面几种垃圾回收器有一个共性就是STW,就是我垃圾回收器在工作的时候其他人都不许动,得等着,我干完了,你们才能继续工作。

CMS的诞生就是试图解决这个问题。然而CMS本身的问题很多,目前任何JDK版本默认的垃圾回收器都不是CMS。

CMS - Concurrent Mark Sweep,并发标记清除。从线程的角度看,CMS进行垃圾回收的时候和工作线程同时进行,但是它依然很慢。

在以往内存较小的时候,速度很快,但现在服务器内存已经很大了,相当于原来在10平米的房间内清理垃圾,现在需要在100平米甚至更大的空间内清理垃圾,即使使用多线程来清理也需要很长的时间。

CMS的四个阶段

  1. CMS initial mark 初始标记阶段

该阶段STW直接找到最根上的对象并标记,其他对象不标记。

  1. CMS concurrent mark 并发标记阶段

GC 80%的时间浪费在并发标记阶段。所以该阶段和工作线程同时运行,客户端可能感觉响应变慢了,但是至少还有点反应。

工作线程一边产生垃圾,一边对垃圾进行标记,这个过程不可能标记完,所以了重新标记阶段。

  1. CMS remark 重新标记阶段

这也是一个STW,在并发标记阶段产生的新垃圾,在该阶段进行重新标记一下,需要工作线程停下来,时间不是很长。

  1. CMS concurrent sweep 并发清理阶段

该阶段由于有工作线程也在运行,因此在执行过程中会产生新的垃圾,这时候的垃圾叫浮动垃圾浮动垃圾在下一次CMS运行的时候再把它清理掉

CMS

CMS的触发条件

老年代分配不下了会触发CMS,其初始标记是单线程,重新标记是多线程。

CMS的缺点

  • Memory Fragmentation 内存碎片

如果内存很大,一旦老年代产生了很多内存碎片的时候,从年轻代进入到老年代的对象就找不到空间了-PromotionFailed。

这时,CMS请出了Serial Old这个上古时代的回收器使用单线程进行标记压缩,那效率就可想而知了。

-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 默认为0 指经过多少次FGC才进行压缩
  • Floating Garbage 浮动垃圾

可以通过参数配置降低触发CMS的阈值。

-XX:CMSInitiatingOccupancyFraction 92% 可以降低该值,让老年代有足够的空间

5.7 G1

G1也能并发进行垃圾回收,与CMS相比,其优点如下:

  • G1在GC过程中会进行整理内存,不会产生很多内存碎片
  • G1的STW更可控,可以指定可期望的GC停顿时间

在G1中将内存区域划分为多个不连续的区域(Region),每个Region内部是连续的。

G1内存区域划分

在划分的区域中H区(Humongous),这表示这些Region存储的是巨大对象(humongous object,H-obj)-大小大于等于region一半的对象

一个Region的大小可以通过参数

-XX:G1HeapRegionSize

设定,取值范围从1M到32M,且是2的指数。如果不设定,那么G1会根据Heap大小自动决定。

5.8 ZGC

ZGC是一种并发的不分代的基于Region且支持NUMA的压缩收集器。因为只会在枚举根节点的阶段STW, 因此停顿时间不会随着堆大小或存活对象的多少而增加

ZGC的目标

  • 垃圾回收停顿时间不超过10ms
  • 无论是相对小的堆(几百MB)还是大堆(TB级)都能应对自如
  • 与G1相比,吞吐量下降不超过15%
  • 方便日后在此基础上实现新的gc特性、利用colored pointers和读屏障进一步优化收集器

6. 参考资料

  1. Getting Started with the G1 Garbage Collector
  2. Java Garbage Collection Basics
  3. https://wiki.openjdk.java.net/display/zgc/Main

首发公众号 行百里er,欢迎老铁们关注阅读指正。也可访问我的 GitHub github.com/xblzer/JavaJourney

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-10-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 行百里er 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 内存分代模型
  • 2. 一个对象的生命历程-从出生到消亡
  • 3. 对象如何进行栈上分配
  • 4. 线程本地分配
  • 5. 常见的垃圾回收器
    • 5.1 Serial
      • 5.2 Serial Old
        • 5.3 Parallel Scavenge
          • 5.4 Parallel Old
            • 5.5 ParNew
              • 5.6 CMS
                • 5.7 G1
                  • 5.8 ZGC
                  • 6. 参考资料
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档