写点什么

架构师训练营第九周 - 作业

用户头像
Eric
关注
发布于: 2020 年 07 月 31 日
架构师训练营第九周 - 作业

请简述 JVM 垃圾回收原理

JVM垃圾回收机制

  1. 回收发生在哪里?

JVM 的内存区域中,程序计数器、虚拟机栈和本地方法栈这 3 个区域是线程私有的,随着线程的创建而创建,销毁而销毁;栈中的栈帧随着方法的进入和退出进行入栈和出栈操作,每个栈帧中分配多少内存基本是在类结构确定下来的时候就已知的,因此这三个区域的内存分配和回收都具有确定性。

那么垃圾回收的重点就是关注和*方法区*中的内存了,堆中的回收主要是对象的回收,方法区的回收主要是废弃常量和无用的类的回收



  1. 对象在什么时候可以被回收?

一个对象不再被引用,就代表该对象可以被回收。目前有以下两种算法可以判断该对象是否可以被回收:



引用计数算法:这种算法是通过一个对象的引用计数器来判断该对象是否被引用了。每当对象被引用,引用计数器就会加 1;每当引用失效,计数器就会减 1。当对象的引用计数器的值为 0 时,就说明该对象不再被引用,可以被回收了。这里强调一点,虽然引用计数算法的实现简单,判断效率也很高,但它存在着对象之间相互循环引用的问题。



可达性分析算法:GC Roots 是该算法的基础,GC Roots 是所有对象的根对象,在 JVM 加载时,会创建一些普通对象引用正常对象。这些对象作为正常对象的起始点,在垃圾回收时,会从这些 GC Roots 开始向下搜索,当一个对象到 GC Roots 没有任何引用链相连时,就证明此对象是不可用的。目前 HotSpot 虚拟机采用的就是这种算法。



在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为了以下四种:



  1. 如何回收这些对象?

JVM 垃圾回收遵循以下两个特性:



自动性:JVM 提供了一个系统级的线程来跟踪每一块分配出去的内存空间,当 JVM 处于空闲循环时,垃圾收集器线程会自动检查每一块分配出去的内存空间,然后自动回收每一块空闲的内存块。



不可预期性:一旦一个对象没有被引用了,该对象是否立刻被回收呢?答案是不可预期的。我们很难确定一个没有被引用的对象是不是会被立刻回收掉。垃圾回收线程在 JVM 中是自动执行的,Java 程序无法强制执行。我们唯一能做的就是通过调用 System.gc 方法来”建议”执行垃圾收集器,但是否可执行,什么时候执行?仍然不可预期



堆的划分



当 Eden 区的空间耗尽, 触发 Minor GC,收集新生代的垃圾。存活下来的对象,被送到 Survivor 区。当发生 Minor GC 时, Eden 区和 from 指向的 Survivor 区中的存活对象会被复制到 to 指向的 Survivor 去,然后交换 from 和 to 指针。



JVM 会记录 Survivor 区中的对象一共被来回复制了几次。如果一个对象被复制的次数达到一个值(对应虚拟机参数 -XX:+MaxTenuringThreshold),该对象会被promo 至老年代。如果带个 Survivor 区被占用 50%(对应虚拟机参数 -XX:TargetSurvivorRatio),较高复制次数的对象也会被 promo 至老年代。



:JDK 11 引入并发、不分代的 ZGC 回收器。



GC 算法



垃圾回收器的实现



  1. 垃圾回收器种类

  • 针对新生代的垃圾回收器

* Serial

* Parallel

* Parallel New

这三个采用的都是标记 - 复制算法。其中,Serial 是一个单线程的,Parallel New 可以看成 Serial 的多线程版本。Parallel Scavenge 和 Parallel New 类似,但更加注重吞吐率。此外,Parallel Scavenge 不能与 CMS 一起使用。



  • 针对老年代的垃圾回收器

* Serial Old

* Parallel Old

* CMS

Serial Old 和 Parallel Old 都是标记 - 压缩算法。同样,前者是单线程的,而后者可以看成前者的多线程版本。CMS 采用的是标记 - 清除算法,并且是并发的。除了少数几个操作需要 Stop-the-world 之外,它可以在应用程序运行过程中进行垃圾回收。在并发收集失败的情况下,Java 虚拟机会使用其他两个压缩型垃圾回收器进行一次垃圾回收。由于 G1 的出现,CMS 在 Java 9 中已被废弃。



  • G1(Garbage First)

是一个横跨新生代和老年代的垃圾回收器。实际上,它已经打乱了前面所说的堆结构,直接将堆分成极其多个区域。每个区域都可以充当 Eden 区、Survivor 区或者老年代中的一个。它采用的是标记 - 压缩算法,而且和 CMS 一样都能够在应用程序运行过程中并发地进行垃圾回收。G1 能够针对每个细分的区域来进行垃圾回收。在选择进行垃圾回收的区域时,它会优先回收死亡对象较多的区域。这也是 G1 名字的由来。



  • ZGC

JDK 11引入,并发、不分代的垃圾回收器



  1. GC 性能衡量指标

* 吞吐量:应用程序所花费的时间和系统总运行时间的比值。

* 停顿时间:垃圾回收器运行时,应用程序的暂停时间。

* 垃圾回收频率:通常垃圾回收的频率越低越好,增大堆内存空间可以有效降低垃圾回收发生的频率,但同时也意味着堆积的回收对象越多,最终也会增加回收时的停顿时间。



  1. 工具

* jmap 输出内存对象

* [GCViewer](gcviewer download | SourceForge.net) GC日志分析

* [GCeasy](Universal JVM GC analyzer - Java Garbage collection log analysis made easy) GC日志分析



  1. GC 调优策略

* 降低 Minor GC 频率

如果在堆内存中存在较多的长期存活的对象,此时增加新生代空间,反而会增加 Minor GC 的时间。如果堆中的短期对象很多,那么扩容新生代,单次 Minor GC 时间不会显著增加。因此,单次 Minor GC 时间更多取决于 GC 后存活对象的数量,而非 Eden 区的大小。



* 降低 Full GC 频率

* 减少创建大对象

* 增大堆内存空间

* 选择合适的垃圾回收器:要求响应时间短,考虑 G1 回收器;要求吞吐量高,考虑 Parallel Scavenge 回收器。



注意:调整 GC 参数前,首先要有性能衡量指标



设计一个秒杀系统,主要的挑战和问题有哪些?核心的架构方案或者思路有哪些?

挑战和问题

  • 稳:高可用,保证秒杀活动顺利完成

  • 准:不会出现超卖,造成运营方损失

  • 预防秒杀器



架构原则:“4 要 1 不要”

  • 数据要尽量少

降低CPU的消耗:压缩、编码,简化秒杀页面,去掉不必要的业务装饰效果

  • 请求数要尽量少

合并 CSS 和 JavaScript,减少需要涉及的域名

  • 路径要尽量短

减少网络调用,降低延时

  • 依赖要尽量少

将系统按重要性分级,防止重要系统被不重要系统拖垮

  • 不要有单点

服务无状态化



思路

  • 动静分离

* 静态数据缓存到离用户最近的地方

* 静态化改造 —— 直接缓存 HTTP 连接



  • 二八原则:有针对性地处理系统的热点

* 热点操作:刷新页面、添加购物车、抢下单。这些操作可分为“读”和“写”两类,读可通过缓存优化,写瓶颈通常在存储层。

热点操作通常集中在下单,成功下单后,支付可以给出一段时间后续进行,避免拖垮支付系统

* 热点数据:例如参加秒杀活动的商品数据

可以通过商业手段,例如让卖家提前挑选出热点商品,针对对这些数据进行缓存;或者通过大数据技术手段,预测热点商品



  • 分级门槛



  • 优化、限制、隔离

* 优化:缓存热点数据

* 限制:对流量进行削峰。通常秒杀活动希望更多的人来参与,但真正能下单抢到商品的人数是预先固定的,对此可以只处理部分下单请求,其它请求直接结束。

* 隔离:将秒杀业务与正常业务隔离,将秒杀系统的服务器群集独立出来



  • 预防秒杀器

秒杀 Detail 页面 URL 带上随机 token,临近秒杀前才放出,设置访问次数上限



  • 应急预案

* 备用带宽、服务器

* 服务降级,当系统容量达到一定程度是,限制或关闭系统的某些非核心功能

* 拒绝服务:万能出错页面 —— “秒杀活动已结束”



用户头像

Eric

关注

给写代码的人写代码 2017.10.17 加入

Clojure

评论

发布
暂无评论
架构师训练营第九周 - 作业