1.问题:gc垃圾回收算法和垃圾回收器的关系?分别是什么谈谈你的看法
gc算法(引用计数/复制/标清/标整)是内存回收的理论,垃圾回收器是算法的落地实现
目前为止没有完美的收集器出现,只是针对具体应用选择合适的收集器,进行分代收集
4种主要垃圾收集器
1.Serial:串行垃圾回收器
2.Parallel:并行垃圾回收器
3.CMS(ConcMarkSweep:并发标记清除):并发垃圾回收器
4.G1
详解:
1.Serial:串行垃圾回收器,它为单线程环境设计并且只能使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不符合生产环境
2.Parallel:并行垃圾回收,多个垃圾收集线程并行工作,此时用户的线程也是暂停的,适用于科学计算/大数据处理首页处理等弱交互场景
3.CMS:并发垃圾回收器,用户线程和垃圾收集程序同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,互联网公司多用它,适用于对响应时间有要求的场景
4.G1特殊
1.STW和Concurrent
G1垃圾回收器:java9的默认垃圾回收器就是G1
将对内存分割成不同的区域然后并发的对其进行垃圾回收
1.查看默认的垃圾回收器:java -XX:+PrintCommandLineFlags -version 打印默认的核心jvm配置
>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=267008576 -XX:MaxHeapSize=4272137216 -XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLarge
PagesIndividualAllocation
-XX:+UseParallelGC (默认的垃圾回收器,并行回收)
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
垃圾收集器
1.新生代的垃圾回收
参数预先说明:
1.DefNew:Default New Generation 默认新生代使用哪种垃圾回收器
2.Tenured:Old养老代
3.ParNew:Parallel New Generation并行默认
4.PSYoungGen:Parallel Scavenge
5.ParOldGen:Parallel Old Generation
1.新生代:
1.1 串行GC(Serial)/(Serial Copying)
1.理论:年轻代和老年代都采用一个线程回收垃圾
回收垃圾时,只有一个垃圾回收线程执行
虽然收集垃圾过程种需要暂停其他的工作线程,但是你它简单高效,对于单个CPU环境来说,
没有线程的交互开销,可以获得最高的单线程垃圾搜集效率,
因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代收集器
2.开启方式:
JVM参数 -XX:+UseSerialGC
3.开启以后默认使用:
Serial(Young区使用)+Serial Old(Old区用)的收集器组合
4.表示:
新生代、老年代都会使用串行垃圾收集器,新生代使用复制算法,老年代使用标记整理算法
1.2 并行GC(ParNew)-->年轻代并行多个线程回收,老年代串行一个线程回收
1.理论:
ParNew收集器其实时Serial收集器新生代的并行多线程版本,最常用的是配合老年代的CMS GC工作,其余行为和Serial收集器完全一致
在垃圾收集过程种也要暂停其他的工作线程
他是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器
如图2所示,年轻代会采用并行(Parallel)多个线程收集垃圾,但是老年代还是采用串行(Serial)只有一个线程回收垃圾
2.开启方式:
-XX:+UseParNewGc 启用ParNew收集器,之影响新生代的收集,不影响养老代
3.开启上述参数,会使用:
ParNew(Yong区使用)+Serial Old的收集器组合(这个垃圾器已经被放弃),新生代采用复制算法,老年代采用标记整理算法
4.参数配置:
-XX:ParallelGCThreads 限制并行垃圾回收线程数量,默认是开启和Cpu数目相同的线程数
1.3 并行回收GC(Parallel)/(Parallel Scavenge)--->新生代老年代都采用并行多线程回收垃圾
1.理论:
Parallel Scavenge收集器类似ParNew 也是一个新生代垃圾收集器,使用复制算法,也是一个并行的多线程垃圾收集器
俗称为吞吐量优先收集器,
吞吐量:比如程序运行了100分钟,垃圾收集时间为1分钟,则吞吐量为99%
2.激活:
-XX:+UseParallelGC或者-XX:+UseParallelOldGC(可互相激活)使用Parallel Scanvenge收集器
3.开启上述参数后:
UseParallelGC(Yong区使用)+UseParallelOldGC(Old区使用),新生代采用复制算法,老年代采用标记整理算法
4.多说一句:
-XX:ParallelGCThreads=数字N 表示启动多少个GC线程
1.串行垃圾回收器(单个线程回收垃圾)
2.并行垃圾回收器(多个线程回收垃圾)
3.并行收集器
老年代的垃圾回收器
老年代的垃圾回收器:
1.Parallel Old收集器:并行
1.1 理论:
Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,Parallel Old在jdk1.6才开始提供
在jdk1.6之前,新生代使用Parallel Scavenge收集器只能搭配老年代的Serial Old收集器,只能保证新生代的吞吐量优先
无法保证整体的吞吐量,在jdk1.6之前(Parallel Scavenge+Serial Old)
Parallel Old正是为了在老年代提供吞吐量优先的垃圾收集器,如果系统对吞吐量的要求较高,JDK1.8后可以考虑新生代使用Parallel Scavenge
和老年代使用Paralle Old的搭配策略,在jdk1.8之后(Parallel Scavenge+Parallel Old)
1.2 启动参数:
-XX:+UseParallelOldGC 使用Parallel Old收集器,设置该参数后,新生代Parallel+老年代 Parallel Old
2.并发标记清除GC(CMS):
2.1 理论
CMS收集器又称之为(Concurrent Markk Sweep:并发标记清除)是一种以获取罪罚回收停顿时间为目标的收集器
适合应用于互联网或者B/S系统的服务器上,这类应用尤其中使服务器的响应速度,希望系统停顿时间最短
CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前的大型应用的首选收集器
2.2 激活:
-XX:+UseConcMarkSweepGC 开启该参数后会自动打开-XX:+UseParNewGC(年轻代的并行垃圾回收)
2.3 开启该参数后:
使用ParNew(Yong区使用)+CMS(Old区使用)+Serial Old的收集器组合,Serial Old将作为CMS出错后的后备收集器
2.4步骤;
1.初始标记:只是标记下GC ROORS能直接关联的对象,速度很快,扔需要暂停所有的工作线程
2.并发标记:和用户线程一起工作,不需要暂停工作线程,只要标记过程,标记全部对象
3.重新标记:修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程
由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
4.并发清除:和用户线程一起,清除gc roots不可达对象,不需要暂停工作线程,基于标记结果,直接清理对象
由于好事最长的并发标记和并发清除过程种,垃圾收集线程可以和用户一起并发工作
所以总体上来看CMS收集器的内存回收和用户线程是一起并发执行的
3.优缺点:
1.优点:并发收集低停顿
2.缺点:
1.并发执行,对CPU资源的压力大 ,由于和用户线程并发运行,CMS在收集和应用线程会同时增加对堆内存的占用,也就是说,
CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败,将触发担保机制,串行老年代带收集器将会以STW的方式进行一次FUll gc
从而造成较大停顿时间
2.采用标记清除会产生大量的内存碎片,标记清除算法无法整理空间碎片,老年代会随着应用时间长被逐渐耗尽
最后不得步通过担保机制对堆内存进行压缩,CMS也停工了安舒-XX:CMSFullGCsBeForeCompaction(默认是0)
来指定多少次CMS收集之后进行一次压缩的Full GC
垃圾收集器的选择:
组合:
1.单CPU或小内存,单机程序
-XX:+UseSerialGC
2.多核CPU,需要最大吞吐量,如后台计算型应用
-XX:+UseParallelGC或者
-XX:+UseParallelOldGC
3.多CPU,追求低停顿时间,需要快速想用,如互联网应用
-XX:+UseConcMarkSweepGC(CMS)
G1
G1垃圾收集器
开启:
-XX:+UseG1GC
以前的垃圾收集器特点:
1.年轻代和老年代是各自独立且连续的内存块
2.年轻代收集使用单eden+s0+s1进行复制算法
3.老年代收集必须要扫描整个老年代区域
4.都是以近可能少而快速地执行GC为设计原则
G1的特性:
1.像CMS收集器一样,都与应用程序并发执行
2.整理空闲空间更快
3.需要更多的时间预测GC停顿时间
4.不希望牺牲大量的吞吐性能
5.不需要更大的Java Heap
G1收集器的设计目标就是取代CMS收集器。它与CMS相比在以下的表现更出色:
1.G1是有一个整理内存的过程的垃圾收集器,不会产生很多的内存碎片,CMS采用的是标记清理的算法,会产生大量的内存碎片
2.G1的STW更可控,G1在停顿删上加了预测机制,用户可以指定期望停顿的时间
G1的设计思想:
主要改变时Eden\Survivor和Tenured等内存区域不再是连续的了,而是编程一个个大小一样的region(区块)
每个区块从1-32m不等,一个区块有可能属于Eden,Survior或者Tenured内存区域
1.G1能充分里用多核cpu,多核环境硬件优势,见谅缩短STW
2.G1整体上采用标记-整理算法,局部通过复制算法,不会产生内存碎片
3.宏观上看G1值种不再区分年轻代和老年代,把内存划分成了多个独立的子区域,可以近似理解成一个棋盘
4.G1收集器里面在整个内存区都混合在一起,单其本身依然在小范围进行年轻代和老年代的区分,保留了新生代和老年代,但是他们不再是物理隔离
而是一部分的区块的集合且不需要区块是连续的,也就是说依然会采用不同的GC方式来处理不同的区域
5.G1虽然也是分代收集器,但是整个内存分区不存在物理上的年轻代和老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备
G1只有逻辑上的分代概念,或者说每个分区都可能随着G1的运行在不同代之间前后切换
Region区域化垃圾收集器:
区域化内存划片Region,整体为一些不连续的内存区域,避免了全内存区的GC操作
核心思想是量堆内存区域分为了大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小
在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定的为某个代服务,可以按需在年轻代和老年代之间切换。
启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB-32MB,且必须是2的幂),默认将整堆分为了2048个分区
大小范围在1MB-32MB,最多能设置204个区域,也能够支持的最大内存为:32MB*2048=64G
G1的内存分配:
G1办法将堆分为了若干个区域(Region),他仍属于分代收集器
1.这些Region一部分包含了新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间
2.这些Region的一部分包含老年代,G1收集器通过将对象从一个区域复制到另外一个区域,完成清理工作。这就意味着,在正常的处理过程中
G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在。
3.在G1中,还有一种特殊的区域,叫Humongous(巨大的)区域,如果一个对象占用的超过了分区容量的50%以上,G1收集器会认为这是个巨型对象。
这些巨型对象默认直接会被分配在老年代,但是如果他是一个短期存在的巨型对象,就会对垃圾收集造出负面的影响,为了解决这一问题,G1划分了一个Humongous区
他用来专门存放巨型对象,如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储,为了找到连续的H区,有时候不得不启动Full gc
G1的常用参数配置:
1.-XX:+UseG1GC
2.-XX:G1HeapRegionSize=n:设置g1区域的带下,值是2的幂,范围是1MB-32MB,目标是根据最小的java堆大小划分出约2048个区域
3.-XX:MaxGCPauseMillis=n:最大GC停顿时间,是个软目标,jvm尽量(但不保证)停顿小于这个时间
4.-XX:InitiatingHeapOccupancyPercent=n:堆占用多少的时候触发gc,默认是45
5.-XX:ConcGCThreads=n:并发使用的线程数
6.-XX:G1ReservePercent=n:设置作为空闲空间的预留内存百分比,比降低目标空间溢出的风险,默认是10%
一般配法:
-XX:UseG1GC -Xmx32g -XX:MaxGCPauseMillis=100
G1和CMS的对比优势:
1.G1不会产生内存碎片
2.是可以精确控制停顿。该收集器是把整个堆(新生代、老年代)划分成了多个固定大小的区域
每次根据停顿时间去收集垃圾最多的区域
JVMGC+SpringBoot微服务的生产部署和调参优化