类加载器ClassLoader负责加载class文件,且其只负责class文件加载,至于它是否可以运行则由Execution Engine决定。
启动类加载器:该类加载器负责加载放在
扩展类加载器:负责加载
应用程序类加载器:负责加载用户类路径(ClassParh)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,这个就是程序中默认的类加载器。
自定义加载器:当我们需要防止java代码被反编译或者是从非标准的来源加载代码(比如字节码放在数据库)就需要用到自定义类加载器。 用户可以继承ClassLoader类,重新findClass方法可以自定义类加载器。
如果一个类加载器收到了类加载的请求,它首先不会尝试自己去加载这个类,而是把这个请求委任给父类加载器去加载,因此所有的类加载应该都是从顶层开始加载,只有父类加载器反馈无法完成类加载请求时,子加载器才会尝试去自己加载。
在对象中添加一个计数器,每当一个地方引用的时候,计数器的值就加一,当引用失效的时候,计数器的值就减一,当计数器为零的时候说明对象是不再被使用了,可以被回收掉。但是这个方案有一个缺陷就是无法解决循环引用的问题(比如A引用了B,然后B又引用了A),这种情况会造成垃圾无法回收。
该算法的基本思路:从GC Roots的根对象作为起始点,从起始点开始,根据引用关系向下搜索,如果某个对象到GCRoots间没有任何的引用链,那么可以证明这个对象是不在被使用的。
可以作为GC Roots的对象包含:
该算法的两个步骤分别为“标记”和“清除”,标记过程就是对象判断是否属于垃圾的一个判定过程,标记完成后统一进行清理。
缺点:执行效率不稳定,如果堆中包含大量对象,这个时间就需要很多的标记和清楚的操作,导致对象越多其执行效率会越低。
标记清除后会产生大量不连续的内存空间的碎片,当空间碎片太多会导致程序运行的时候分配大对象的时候找不到满足条件的连续空间而不得不再提前触发一个垃圾收集动作。
它将可用的内存按照容量划分为两个相等的两个区域,每次使用其中一块,当空间用完了后就将其还存活的对象复制到另外一个区域当中,然后把使用过后的空间一次性清理掉。如果内存中的大多数对象是存活的话,那么会消耗大量的时间在内存间的复制,所以这种算法适用于少数存活对象的区域。这种方式分配内存的时候不用考虑有空间碎片的内存情况,只要移动堆顶指针。按照顺序分配即可。
缺点:复制算法将可用的内存空间缩小了一半。
标记整理算法就将标记的可存活对象向内存空间的一端移动,然后直接清理掉边界外的内存。
根据对象的生存周期,根据新生代和老年代对象不同的特性选择不同的垃圾回收算法,比如新生代采用复制算法,老年代使用标记清除或者标记整理算法。
是最基本、发展历史最悠久的收集器,一个单线程的收集器。在进行垃圾收集时必须暂停其他所有的工作线程,直到收集结束为止。
Serial/Serial Old收集器运行流程
其实就是Serial收集器的多线程并行版本。
ParNew/Serial Old收集器运行流程
新生代收集器,使用的也是复制算法的收集器,又是并行的多线程收集器,此收集器的目标是达到一个可控制的吞吐量。所谓的吞吐量就是CPU用于运行用户代码的时间与CPU总消耗的时间的比值,例如虚拟机总共运行了100分钟,其中垃圾收集花掉了1分钟,那么吞吐量就是99%。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
Parallel Scavenge/Parallel Old收集器运行流程
Serial收集器的老年代版本,也同样是一个单线程收集器,使用的是标记-整理算法。
Serial/Serial Old收集器运行流程
Parallel Old收集器的老年版本,使用多线程和标记-整理算法。
Parallel Scavenge/Parallel Old收集器运行流程
CMS是一个老年代收集器,是一种以获取最短回收停顿时间为目标的收集器,CMS收集器是基于标记-清除算法实现的,它从总体上来说CMS收集器的内存回收过程是与用户线程一起并发执行的。但是需要注意CMS不能和Parallel Scavenge收集器配合工作。
CMS收集器运行流程
CMS收集器的流程分为以下四步:
初始标记:该阶段仅仅只标记以下GC Roots能直接关联的对象,时间非常短。
并发标记:该阶段从GC Roots的直接关联对象开始遍历整个对象图的过程,这个是和用户线程并发运行的。
重新标记:该阶段是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的时间通常是大于初始标记而小于并发标记。
并发清除:该阶段清理删除掉被标记的已经判断死亡的对象,这个阶段也是和用户线程并发进行的。
CMS是一款非常优秀的垃圾收集器,但是还是会有下面所示的三个缺点:
虽然G1也仍是遵循分代理论设计的,但是G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的堆划分为多个大小相等的独立区域,每个区域都可以根据需要扮演Eden区、Survivor区或者是老年代空间的角色,收集器能根据不同的角色的区域采用不同的策略处理。
Region中还有一类特殊的Humongous区域,专门用来存储大文件,G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象,每个Region的大小可以通过-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,而对于那些大对象,将会被存在N个连续的HumongousRegion中。G1的大多数行为都把Humongous Region作为老年代的一部分进行看待。
G1收集器运行流程
G1垃圾收集器运行过程
初始标记:仅仅只是标记一个GC Roots能直接关联到的对象,这个阶段需要停掉线程,但是这个时间非常短,而且是借用进行Minor GC的时候同步完成的,所以这个阶段并没有额外的停顿。
并发标记:从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这个阶段比较耗时,但是由于其是并发的,可以与用户程序同时进行而无需停顿。
最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录
筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户的期望停顿时间来定制回收计划,可以自由选择任意多个Region构成收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧的Region的全部空间。这里涉及到存活对象的移动,是必须暂停用户线程,由多条收集器线程并行执行的。
可以作为了解的垃圾收集器
Shenandoah收集器:并发标记、并发回收、并发引用更新。
ZGC收集器:并发标记、并发预备重分配、并发重分配、并发重映射。
如果觉得本篇文章对你有帮助的请给作者点个关注,每周都会有干货输出哦!