JVM中的垃圾回收机制
Posted IT94
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM中的垃圾回收机制相关的知识,希望对你有一定的参考价值。
在Java中,由于引入了JVM,所以我们不用再向C/C++那样创建对象之后手动的去申请和释放内存,这一切都交给了虚拟机来完成,而为了我们更好的了解对象的内存分配情况,更是为了优化系统,所以我们必然要去了解JVM的内存结构以及垃圾回收机制。
JVM内存结构
我们通过两张图来记忆其内存结构:
JVM内存分为五部分:
线程私有的:虚拟机栈、本地方法栈、程序计数器
本地方法栈:与虚拟机栈相似,只是它服务的对象是本地方法(Native Method)。
线程共享的:堆和方法区
堆:堆中保存的是java对象本身以及数组。
方法区:存储的是类的信息(类名、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码。(注:方法区在JDK1.8之后被
metaspace
元数据区取代,存在于本地内存,其中存的是类的元数据,而常量池和类的静态变量存在Java堆中)。重点说一下java堆和方法区
堆又分为新生代和老年代,通俗的讲方法区又称为永久代(方法区不等价于永久代,只是因为方法区是由永久代来实现的),新生代又分为
eden
区和servivor-from
、servivor-to
区,在垃圾回收时使用。eden区存放的是新创建的对象,如果新创建的对象占用空间很大,则直接进入老年代,如果eden区空间存满,则会发生新生代的垃圾回收,即
MinorGC
。servivor-from区存放的是上一次垃圾回收的存活对象,下一次垃圾回收时扫描此部分空间。
servivor-to区存放的是由eden区和servivor-from区扫描的存活对象复制过来的。
老年代存放的是生命周期长的对象,一是对象创建时eden区放不下会直接进入老年代,二是在新生代垃圾回收多次后达到放入老年代阈值时存入老年代,默认此阈值时15。
老年代的垃圾回收不会频繁的发生(老年代垃圾回收叫
MajorGC
),因为老年代存放的是生命周期长的对象。
什么时候发生垃圾回收
新生代垃圾回收时机
eden区空间不足
老年代垃圾回收时机
新创建的大对象放入老年代,而老年代空间不足时发生
新生代对象达到晋升阈值时,进入老年代时空间不足
永久代
主程序运行期间不会对永久代进行垃圾回收,只会随着类信息的不断加载而胀满,最终导致OOM。
查找存活对象的方法
引用计数法
在堆中存储对象时,会在对象头维护一个计数器,当增加一个该对象的引用计数器就会加一,引用关系消失则计数器减一,当计数器值为零时就可以标记该对象为可回收对象。但是引用计数法存在一个问题,就是循环引用:如果A对象引用了B对象,而B对象引用了A对象,此时就造成了类似死锁的循环,计数器永远不会为零,这就导致了GC永远不会回收这两个对象
可达性分析
此方法通过以GCRoots
为起点查找关联对象,如果存在到达对象的路径,就说明该对象时存活对象,而找不到到达对象的路径则说明对象可能是可回收对象,因为标记为可回收对象要求必须至少经过两次不可达标记,如果两次之后依然是可回收对象,就将面临垃圾回收。
垃圾回收算法
复制算法(copying)
将堆空间分为等大小的两部分,每次只使用其中的一块,如果一块内存满了之后就会将存活对象复制到另一块上去,把已使用的内存清空。此算法可以解决内存碎片化的缺陷。
如图:
标记-清除算法(mark-sweep)
首先是标记可回收对象,然后将可回收对象所在的空间清空。如图:
标记-整理算法(mark-compact)
先标记可回收对象,回收完成后将存活对象移向内存的一端,然后清空端外内存。如图:
分代回收算法
将内存空间根据对象生命周期的长短划分为不同的区域,堆内存分为新生代和老年代。新生代会发生频繁的GC,而老年代GC频率较低,每次回收对象较少。所以可以根据不同的区域来使用不同的垃圾回收算法。
分区回收算法
将堆空间划分为相同大小的小区间,每个小空间单独使用,单独回收,这样可以控制每次回收的小区间个数,从而控制每次GC用户线程的停顿时间。
STW:stop the world,GC时会使用户线程停顿,此过程叫做STW。
垃圾收集器
新生代和老年代采用不同的垃圾收集器
新生代:serial收集器、 parNew收集器、 parallel scavenge收集器
老年代:serial old收集器、 parallel old收集器、 CMS(concurrent mark sweep)收集器
G1(garbage first)收集器
Serial收集器
Serial收集器是单线程的、采用复制算法。在收集过程中只使用一条线程,并且收集过程中必须暂停其他的工作线程,直到垃圾收集结束。
ParNew收集器
这种收集器其实是Serial收集器的多线程版,也是采用复制算法,使用多条线程去完成垃圾收集,同时收集过程中也会暂停其他工作线程。默认启用CPU个数条线程,可以使用-XX:ParallelGCThreads
参数来修改启用的线程数。
Parallel Scavenge收集器
Parallel Scavenge收集器也是采用多线程的复制算法,和ParNew收集器的不同在于可以控制吞吐量(吞吐量=用户代码运行时间/(用户代码运行时间+垃圾收集时间)),同样也会暂停工作线程。
Serial Old收集器
单线程的,采用标记-整理算法,会暂停用户线程。
Parallel Old收集器
多线程的,标记-整理算法,可以控制吞吐量,会暂停工作线程。
CMS收集器
多线程的,采用标记-清除算法,主要是为了保证垃圾收集期间最短的工作线程停顿。
过程:
初始标记,标记GCRoots可以直接关联的对象,需要暂停用户线程。
并发标记,从上一阶段标记的对象出发,标记所有可达到的对象,此阶段和用户线程并发执行,不会暂停用户线程。
重新标记,修正并发标记阶段产生变动的对象,此阶段多线程执行,暂停用户线程。
并发清除,和用户线程并发执行,清除GCRoots不可达对象,不会暂停用户线程。
G1收集器
G1收集器相对于CMS收集器的改进:
采用标记-整理算法,不会产生内存碎片。
可以精确控制STW的时间,并且不会牺牲吞吐量,该收集器不会全区域回收,而是把堆空间分为固定大小的几个区域,跟踪其垃圾回收进度,然后维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。
最近热文:
如果你喜欢本文。
请长按二维码,关注IT94
以上是关于JVM中的垃圾回收机制的主要内容,如果未能解决你的问题,请参考以下文章