第九周作业

用户头像
路人
关注
发布于: 2020 年 08 月 05 日

JVM的垃圾回收

之前专门看过垃圾回收的内容,在此修改贴出:



目前 Java 虚拟机的主流垃圾回收器采取的是可达性分析算法。这个算法的实质在于将一系列 GC Roots 作为初始的存活对象合集(live set),然后从该合集出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程我们也称之为标记(mark)。最终,未被探索到的对象便是死亡的,是可以回收的。



GC Roots 包括(但不限于)如下几种:



  • Java 方法栈桢中的局部变量;

  • 已加载类的静态变量;

  • JNI handles;

  • 已启动且未停止的 Java 线程。



虽然可达性分析的算法本身很简明,但是在实践中还是有不少其他问题需要解决的。比如说,在多线程环境下,其他线程可能会更新已经访问过的对象中的引用,从而造成误报(将引用设置为 null)或者漏报(将引用设置为未被访问过的对象)。



怎么解决这个问题呢?在 Java 虚拟机里,传统的垃圾回收算法采用的是一种简单粗暴的方式,那便是 Stop-the-world,停止其他非垃圾回收线程的工作,直到完成垃圾回收。这也就造成了垃圾回收所谓的暂停时间(GC pause)。



Java 虚拟机中的 Stop-the-world 是通过安全点(safepoint)机制来实现的。当 Java 虚拟机收到 Stop-the-world 请求,它便会等待所有的线程都到达安全点,才允许请求 Stop-the-world 的线程进行独占的工作。



Java几种状态下的安全点设置和检查:



  • JNI本地代码:Java 虚拟机仅需在JNI的 API 的入口处进行安全点检测(safepoint poll),测试是否有其他线程请求停留在安全点里,便可以在必要的时候挂起当前线程。

  • 解释执行字节码:字节码与字节码之间皆可作为安全点。Java 虚拟机采取的做法是,当有安全点请求时,执行一条字节码便进行一次安全点检测。

  • 执行即时编译器生成的机器码:时编译器需要插入安全点检测,以避免机器码长时间没有安全点检测的情况。HotSpot 虚拟机的做法便是在生成代码的方法出口以及非计数循环的循环回边(back-edge)处插入安全点检测。

  • 线程阻塞:阻塞的线程由于处于 Java 虚拟机线程调度器的掌控之下,因此属于安全点。



垃圾回收安全点检测为什么不是越多越好?



  1. 第一,安全点检测本身也有一定的开销。

  2. 第二,即时编译器生成的机器码打乱了原本栈桢上的对象分布状况。因为机器码还需提供一些额外的信息。



注:



  1. 不同的即时编译器插入安全点检测的位置也可能不同。

  2. 除了垃圾回收之外,Java 虚拟机其他一些对堆栈内容的一致性有要求的操作也会用到安全点这一机制。



垃圾回收的三种方式:



  1. 清除(sweep),即把死亡对象所占据的内存标记为空闲内存,并记录在一个空闲列表(free list)之中。当需要新建对象时,内存管理模块便会从该空闲列表中寻找空闲内存,并划分给新建的对象。有两个缺点:造成内存碎片;分配效率较低。

  2. 压缩(compact),即把存活的对象聚集到内存区域的起始位置,从而留下一段连续的内存空间。这种做法能够解决内存碎片化的问题,但代价是压缩算法的性能开销。

  3. 复制(copy),即把内存区域分为两等分,分别用两个指针 from 和 to 来维护,并且只是用 from 指针指向的内存区域来分配内存。当发生垃圾回收时,便把存活的对象复制到 to 指针指向的内存区域中,并且交换 from 指针和 to 指针的内容。堆空间的使用效率极其低下。



简单来说,就是将堆空间划分为两代,分别叫做新生代老年代。新生代用来存储新建的对象。当对象存活时间够长时,则将其移动到老年代。



对于新生代,我们猜测大部分的 Java 对象只存活一小段时间,那么便可以频繁地采用耗时较短的垃圾回收算法,让大部分的垃圾都能够在新生代被回收掉。



当真正触发针对老年代的回收时,Java 虚拟机往往需要做一次全堆扫描,耗时也将不计成本。



新生代:



新生代又被划分为 Eden 区,以及两个大小相同的 Survivor 区



将所有脏卡的标识位清零。



Java 虚拟机中的垃圾回收器



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



针对老年代的垃圾回收器也有三个:刚刚提到的 Serial Old 和 Parallel Old,以及 CMS。Serial Old 和 Parallel Old 都是标记 - 压缩算法。同样,前者是单线程的,而后者可以看成前者的多线程版本。CMS 采用的是标记 - 清除算法,并且是并发的。



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



秒杀系统(补一下)

秒杀系统的主要问题在于短暂的访问增加会对现有的系统架构造成冲击,甚至会击垮数据库。同时,如果为了修改秒杀,修改现有的系统,又是复杂的。另外,秒杀系统还有个严重的安全问题,就是防止提前下单。



解决方法:

1.独立部署,不依赖以前的系统进行改造;

2.页面资源静态化,避免访问数据;

3.加上阀,分层限流,保证最终访问服务器的请求很少;

4.动态生成买单url;

5.用CDN等,减少带宽,防止耗尽。

用户头像

路人

关注

还未添加个人签名 2018.07.26 加入

还未添加个人简介

评论

发布
暂无评论
第九周作业