Java线程池

线程池是什么? 线程池用于多线程处理中,它可以根据系统的情况,可以有效控制线程执行的数量,优化运行效果。线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。 ...

2020-12-16 · 13 分钟

再回首CMS垃圾回收

前言 之前学习JVM垃圾回收时,主要是过了一遍垃圾收集算法,比如复制算法,标记-清除算法,标记-整理算法,在此基础上可以增加分代,每代采取不同的回收算法,以提高整体的分配和回收效率。然后过了一遍JVM中的垃圾收集器,比如Serial、Parallel Scavenge、Parallel New、CMS、G1等。 自认为垃圾收集就是根据GC Root标记所有可达的对象,然后把所有没有标记的对象清除就ok了。是不是很简单。事实上垃圾收集也就是这么一回事,但是很多时候说起来简单,做起来却会出现很多问题。这篇文章就是记录我对CMS垃圾收集器的一些疑问并学习的过程。 首先看一下CMS的整体流程(具体每个流程的详情就自行了解吧) 如何进行标记? 最近在看Golang的GC算法实现,里面用到了三色标记法,但是在我的知识库中对三色标记法有这个概念,是的,我只知道这个概念,不知道三色标记法是怎么一个流程,也不知道三色标记法在GC中怎么与运行的。于是就开始了我的探险之旅。 在搜索了一下三色标记法(具体可以看一下文末参考文档中三色标记法与读写屏障了解详情)后,发现现代追踪式(可达性分析)的垃圾回收器几乎都借鉴了三色标记的算法思想,CMS垃圾收集器也不例外。 GC Root有哪些? 我们知道怎么进行标记了,但最初标记的时候需要一些根据才行啊,这些根据就是我们收的GC Root。GC Root有哪些?网上有很多的答案,我的理解就是 当前活跃调用栈中的指向对象的引用 一些不会发生改变的数据所指向的引用 这里我使用的是引用,而不是对象,因为R大是这样说的(具体的问题见参考文档java的gc为什么要分代?) 所谓“GC roots”,或者说tracing GC的“根集合”,就是一组必须活跃的引用。 例如说,这些引用可能包括: 所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。 VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。 JNI handles,包括global handles和local handles (看情况)所有当前被加载的Java类 (看情况)Java类的引用类型静态变量 (看情况)Java类的运行时常量池里的引用类型常量(String或Class类型) (看情况)String常量池(StringTable)里的引用 注意,是一组必须活跃的引用,不是对象。 现在知道了GC Root,但是我们都知道有分代的概念,新生代的gc和老年的代的gc回收的区域是不一样,那么这里的GC Root是不是应该不一样呢?肯定是不一样的。 首先看一下新生代的GC 新生代的区域一般都比较小,而且对象的存活率都比较低,所以按照前面说的GC Root在新生代的区域扫描就行了。但是会有一个问题?老年代存在引用新生代对象的可能啊?如果只扫描新生代的区域,会漏掉被老年代引用的对象,这些对象就会被清除掉,这是不允许的。 如果这样的话,那是不是扫描一下老年代的对象,看是否引用新生代的对象是不是就ok了?嗯这么做肯定是ok的,但是老年代一般很大,而且存活的对象很多,会导致扫描占用很长的时间。那这个问题如何解?JVM是如何避免Minor GC时扫描全堆的? 经过统计信息显示,老年代持有新生代对象引用的情况不足1%,根据这一特性JVM引入了卡表(card table)来实现这一目的。如下图所示: 卡表的具体策略是将老年代的空间分成大小为512B的若干张卡(card)。卡表本身是单字节数组,数组中的每个元素对应着一张卡,当发生老年代引用新生代时,虚拟机将该卡对应的卡表元素设置为适当的值。如上图所示,卡表3被标记为脏(卡表还有另外的作用,标识并发标记阶段哪些块被修改过),之后Minor GC时通过扫描卡表就可以很快的识别哪些卡中存在老年代指向新生代的引用。这样虚拟机通过空间换时间的方式,避免了全堆扫描。 所以新年代GC的GC Root包含2部分 新生代中满足GC Root定义的对象 卡表中老年代引用新生代的对象 老年代的GC 前面我们说了新生代的gc,我们以同样的思路来看看老年代的gc,老年代的GC Root如何来标记呢?只扫描老年代可以吗?当然是不行的,因为新生代中也可能存在老年代对象的引用,好在新生代并不大,所以老年代GC的时候还需要扫描一遍新生代。 所以老年代GC的GC Root包含2部分 老生代中满足GC Root定义的对象,如图节点1; 标记年轻代中活着的对象引用到的老年代的对象(指的是年轻代中还存活的引用类型对象,引用指向老年代中的对象)如图节点2、3; 并发标记的好坏? 标记作为垃圾回收的第一步,现在知道如何进行标记,接下来就是遍历这些对象,将所有未标记的对象清理就完成GC了。 然而事实上并没有这么简单,如果标记的时候是STW的,那就是这么简单,但是如果标记过程都STW会造成暂停时间过长,给人的感觉就是系统一卡一卡的。 于是就把标记的过程改成并发的进行,也就是CMS中并发标记的过程,然而这就是一切复杂问题的源头。虽然并发标记提升了标记的效率,但是因此却引发了一系列的问题。 因为并发标记时,gc线程和用户线程是并行的,所以在这个过程中会出现下面的情况(需要了解三色标记法与读写屏障): ...

2020-11-27 · 1 分钟