1. 垃圾回收算法
GC和FGC的区别
次数上频繁收集Young区,次数上较少收集Old区,基本不动元空间。GC(YGC)是指新生代的垃圾回收,GC很频繁,因为大多数的Java对象存活时间都很短,所以GC的回收速度很快、也很频繁。FGC是指养老区(Old)的垃圾回收,GC回收速度不频繁,也不快,因为要扫描整个老年区的空间,所以它的速度比GC慢10倍左右。
GC的四大算法引用计数法、复制算法(Copying)、标记清除(Mark-Swap)、标记压缩(Mark-Compact)
1.1 引用计数法
- JVM的实现一般不使用这种方法,应用在微软的COM/ActionScript3/Python...
- 有被引用这个对象,计数器就+1,没有被引用的就被回收了
缺点如下: - 1.每次对对象进行赋值的时候都要将计数器+1,且计数器本身也有消耗
- 2.较难处理循环引用(A引用B,B引用A)
System.gc(); //手动唤醒GC,禁用手动GC,JDK1.8的垃圾回收已经很智能了
//不是立刻执行,也是要看系统的调度,类似创建了线程,也不是立刻执行,得看系统的调度
1.2 复制算法
- 新生代(Young)中使用的GC就是使用的复制算法。
-XX:MaxTenuringThreshold //设置对象再新生代中存活代数,默认是15,最大也就是15,因为markword只留了4bit给其标识
- 复制算法的基本思想是将内存分为两块,每次只用其中一块,这块内存用完,就将或者的对象复制到另外一块上去。复制算法不会产生内存碎片,但是耗空间。
- 从根集合(GCRoots)开始,通过Tracing从FROM区找到存活对象,拷贝到TO区中;FROM区和TO区交换,下次内存分配继续从TO开始。
1.3 标记清除算法
- 思想是:先从内存中标记出来要回收的对象,然后进行统一回收。
- 标记清除算法空间节约出来了,但是会产生内存碎片。而且要扫描两次,一次标记,一次清除,浪费了时间。
1.4 标记压缩算法
- 思想是:先从内存中标记出来要回收的对象,然后进行统一回收,之后对剩下来的存活的对象进行整理(丢在同一侧)
- 缺点:效率不高,需要一定对象的成本,耗时比较严重。
- 改进:标记-清除-压缩算法:标记清除和标记压缩算法的折中,进行多次的GC后才压缩。
JVM的GC用的是哪种方法?
新生代GC使用复制算法,在老年代FGC使用标记压缩、标记清除算法
2. 垃圾回收算法和垃圾回收器的关系?
GC算法(引用计数/复制/标记清除/标记整理)是内存回收的方法论,垃圾回收器就是这些GC算法的落地实现。到目前为止还没有完美的垃圾回收器出现,更加没有万能的收集器,只是针对不同场合选用最合适的垃圾收集器。
3. 主要的垃圾收集器
参数 | 新生代收集器 | 新生代算法 | 老年代收集器 | 老年代算法 |
---|---|---|---|---|
-XX:+UseSerialGC | Serial | 复制 | SerialOldGC | 标整 |
-XX:+UseParNewGC | ParNew | 复制 | SerialOldGC | 标整 |
-XX:+UseParallelGC或-XX:UseParallelOldGC | Parallel(Scavenge) | 复制 | ParallelOldGC | 标整 |
-XX:UseConcMarkSweepGC | ParNew | 复制 | CMS+Serial Old(SerialOld为CMS出错的后备) | 标清 |
-XX:UseG1GC | 整体上使用标整算法 | 局部是通过复制算法,不会产生内存碎片 |
3.1 Serial-串行收集器
为单线程设计并且只使用一个线程进行垃圾回收,会暂停所有的用户线程,不适合用在服务器环境,运行在Client模式下的JVM是个不错的选择,在用户的桌面应用场景中使用串行收集器也是可以接受的。
3.2 Parallel-并行收集器
是Serial的多线程版本,和Serial共享很多源码,多个垃圾收集线程并行工作,此时用户线程是暂停的,停顿时间比Serial短,效率更高适合用于科学计算/大数据处理等弱交互场景。在单核CPU下,可能并行收集器比串行收集器还慢。
3.3 CMS-标记并发清除-ConcMarkSweep)
用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,互联网公司多用它,适合对响应时间有要求的场景。(有一段还是会暂停,但是比较短),在2020年3月的jdk14中CMS被删除。
3.4 G1
G1垃圾回收器将堆内存分成不同的区域,然后并发的对其进行垃圾的回收。
4.怎么查看服务器的垃圾回收器?
使用java -XX:+PrintCommandLineFlags -version
查看初始配置或者jinfo -flag PrintCommandLineFlags pid
查看具体的应用程序的参数。Java8默认使用的是Parallel,即并行垃圾回收。
5. 垃圾收集器的使用
5.1 Serial(新生代)+SerialOld(养老区)
开启配置的参数-XX:+UseSerialGC
,整个过程都会停掉用户线程(STW)。
5.2 ParNew(新生代)+SerialOld(养老区)
新生代采用复制算法(多个线程),暂停所有用户线程(STW),老年区采用标记整理算法(单个线程),暂停所有用户线程(STW)。 开启配置的参数-XX:+UseParNewGC
,可以使用-XX:+ParallelGCThreads
限制线程数,默认开启和CPU核心相同的线程数,但是ParNewGC这种默认搭配的方式已经不推荐使用了。
5.3 ParNew(新生代)+CMS(养老区)
使用-XX:+UseConcMarkSweep
开启CMS收集器
//CMS-并发标记清除,是一种以获得最短回收停顿时间为目标的收集器,适合互联网网站和B/S的服务器上,这类应用注重服务器的响应速度,希望停顿时间最短
//CMS非常适合堆内存大、CPU核数多的服务端应用,也是G1出现之前大型应用的首选收集器
//开启之后会默认打开ParNew,在新生代中使用的收集器(为什么不整合ParallelScavenge?因为底层不能兼容)
//在垃圾收集阶段用户线程没有中断,所以在CMS运行过程中,还应该保证用户线程有足够的内存可用。
//不能像其他收集器一样等到老年区满了才回收,而是应该设定一个阈值,便开始回收,以确保在垃圾回收的过程中用户程序有足够的空间运行。
//要是CMS运行期间预留的内存不够程序运行的需要,还会开启SerialOld收集器,作为CMS出错的后备收集器
//CMS的四个步骤:
//1.初始化标记(CMS initial mark),会停掉用户线程(STE)
//2.并发标记(CMS concurrent mark),不会停掉用户线程,会和用户线程一起进行
//3.重新标记(CMS remark),用来标记出来之前标记了,但是现在还在使用的一些对象,会停掉用户线程(STW)
//4.并发清除(CMS sweep),和用户线程一起进行,清除GCRoots不可达对象
CMS的优缺点
优点:并发收集停顿低
缺点:并发执行,对CPU压力大,采用的标记清除算法会产生内存碎片
既然CMS使用标记压缩算法会产生内存碎片,那为什么还要使用标记压缩算法而不使用标记整理算法?因为CMS的清理过程和用户线程的执行是在一起进行的,如果你把对象整理了,对象的内存位置发生改变,用户线程就不能正常执行了。标整算法适合使用在STW情况下。
5.4 Parallel Scavenge(新生代)+ParallelOld(养老区)
JDK1.8默认使用,关注吞吐量,和ParNew的区别在于ParallelScavenge有自适应调节机制:JVM会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:+MaxGCPauseMills)
或最大的吞吐量。
//关注吞吐量,和ParNew的区别在于ParallelScavenge有自适应调节机制:JVM会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:+MaxGCPauseMills)或最大的吞吐量。
//使用-XX:+UseParallelGC或-XX:+UseParallelOldGC可以互相激活去使用Parallel Scavenge收集器。
//可以指定GC的线程,使用-XX:ParallelGCThreads=N去调整,CPU>8,N=5/8,CPU<8,N=实际个数
//ParallelScanvenge和ParallelOld使用的是两套算法,完全不一样
//适合在后台运行而不需要太多交互任务的任务,比如科学计算、批量处理。也是Stop the world。
//常见在服务器环境下使用
5.5 G1(新生代+养老区)
使用参数-XX:+UseG1GC
开启G1收集器,在Young区和Old区都能使用,使用G1之后,Heap只有两层,region/Metaspace,以前的Young和Old都包含在region中。
以前的垃圾收集器的特点
//以前的收集器的特点:
//1.Young区和Old区是各自独立并且连续的内存块
//2.年轻代收集使用Eden+S0+S1进行复制算法
//3.老年代收集必须扫描整个老年代区域
//4.都是以尽量少而快速地执行GC为设计原则
G1的特点
//G1的特点:
//像CMS一样,可以与应用程序线程并发进行
//整理空闲的空间更快
//需要更多时间来预测GC的停顿时间
//不希望牺牲大量的吞吐性能
//不需要更大的Java Heap
//1.利用多CPU、多核的硬件优势,尽量缩短STW。
//2.整体采用标记整理算法,局部采用复制算法,不会产生内存碎片
//3.G1不再划分Eden、S0、S1,改成一个个的region
//4.G1收集器里面整个内存都混合在一起了,但是本身小范围内是存在Young和Old的区分,保留了新生代核老年代。
// 但他们不再是物理隔离的而是一部分region的集合并且不需要region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。
//5.G1也是分代收集器,但是在整个内存分区不存在物理上的年轻代和老年代的区别,也不需要独立的To区来做复制的准备
// G1只有逻辑上的分代概念,每个分区都可能随G1的运行在不同代之间切换
//G1(Garbage-First)收集器是一款面向服务端应用的收集器,聚焦于多处理器和大容量的内存中。
//在实现高吞吐量的同时,尽可能地满足垃圾暂停时间的要求
//G1的目的是为了取代CMS的收集器,它同CMS相比,在以下方面比较出色:
//1.G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片
//2.G1的Stop the World(STW)更短,G1在停顿时间上添加了预测机制,**用户可以指定停顿的时间**
//在jdk9中将G1变成默认的垃圾回收器以替代CMS
//G1的主要改变是Eden、Survivor和Tenured不再连续了,而是被分成了大小一样的region。
//每个region从1M-32M不等,一个region有可能属于Eden、Survivor或者是Tenured
//在堆的使用上,G1并不要求对象的存储一定是物理上的连续,只需要逻辑上连续即可。
//启动时可以通过参数-XX:G1HeapRegionSize=n指定region分区的大小(1-32M,且必须是1<
G1收集器的过程
//G1步骤(和CMS类似)
//1.初始化标记(CMS initial mark),会停掉用户线程
//2.并发标记(CMS concurrent mark),不会停掉用户线程,会和用户线程一起进行
//3.最终标记(CMS remark),用来标记出来之前标记了,但是现在还在使用的一些对象,会停掉用户线程
//4.筛选回收,和用户线程一起进行,根据时间来进行价值最大化的回收
更多参数:
//-XX:+UseG1GC
//-XX:G1HeapRegionSize=n //Region区域大小,1-32M,且是1<
5.6 如何选择合适的回收器?
//单CPU或者小内存,单机程序
-XX:+UseSerialGC
//多CPU,需要最大吞吐量,比如后台计算型的应用
-XX:+UseParallelGC或-XX:+UseParallelOldGC
//多CPU,追求最低的停顿,虚快速响应,如互联网应用
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC