一.在JVM中什么是垃圾?如何判断一个对象是否可被回收?哪些对象可以作为GC Roots的根
垃圾就是在内存中已经不再被使用到的空间就是垃圾.
1.引用计数法:
内部使用一个计数器,当有对象被引用+1,没有就-1,但是没有办法解决循环引用的问题,JVM不采用此类回收法
2.枚举根节点可达性分析(GC Root) 它必须是一组活跃的引用
思路:通过一系列名为GC Roots的对象作为起始点,从这个被称为GC Root的对象开始向下进行搜索,如果一个对象达到GC Roots
没有任何的引用链相连时,这说明此对象不可用,也即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到
的对象就判定为存活,没有遍历到的就判定为死亡
3.
1.虚拟机栈,栈帧中的局部变量区,也称为局部变量表中引用的对象
2.方法区中的类静态属性所引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI(native方法)所引用的对象
二.如何查看服务器JVM参数的默认值,如何查看正在运行的Java程序的参数?JVM中的常用参数有哪些?可否举例说明
1.分为三种参数类型
标配参数(-version,-help,-showversion)
X参数(-Xint 解释执行,-Xcomp 第一次使用就编译成本地代码,-Xmixed 混合模式)
XX参数
布尔值类型 -XX:+/- 某个属性值 (+表示开启,-表示关闭)
键值对类型 -XX:某个参数 = 某个值
2.分为两个步骤
jps -l 查看到正在运行的Java程序的PID
jinfo -flag -具体参数 PID 查看当前正在运行的Java程序的当前参数的信息
jinfo -flags PID 查看当前正在运行的Java程序的所有配置信息
3.
boolean类型的参数
PrintGCDetails 是否打印GC的细节
UseSerialGC 是否使用串行垃圾回收器
key-value类型参数
MetaSpaceSize 设置元空间的大小
MaxTenuringThreshould 设置新生代对象经过多少次可以晋升到老年代
查看默认的参数
java -XX:+PrintFlagsInitial -verion 查看Java虚拟机在出厂时候的参数配置
其中 = 表示值是多少/是否开启对应功能 := 表示已经被JVM获取手动修改过的参数
java -XX:+PrintCommandLineFlags 查看JVM默认的GC算法,jdk1.8 默认server端使用的是串行GC,在1.10之后统一使用G1垃圾收集器
常用参数配置
-Xms 设置初始堆内存大小,默认为主物理内存的1/64 等价于 -XX:InitialHeapSize
-Xmx 设置堆的最大分配内存,默认为主物理内存的1/4,等价于 -XX:MaxHeapSize
-Xss 设置单个线程的栈的大小,一般默认为512k-1024k,取决于操作系统,Linux/Unix默认为1024k,Windows根据虚拟内存大小来决定,默认出厂值为0
等价于 -XX:ThreadStackSize
-Xmn 设置年轻代的大小,一般不用更改
-XX:MetaSpaceSize 设置元空间的大小,元空间的本质和永久代类似,都是对JVM中方法区的实现,二者的区别在于,元空间并不在虚拟机中,使用的是本地
的主物理内存,因此在默认情况下,元空间的大小仅受本地内存空间的限制
-XX:PrintGCDetails 打印出GC收集的详细日志信息
-XX:SurvivorRatio 设置Eden区的比例占多少,S0/S1相同
-XX:NewRatio 设置年轻代和老年代在堆解构的占比
-XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的1/3
-XX:NewRatio=4 新生代占1,老年代占4,年轻代占整个堆的1/5
-XX:MaxTenuringThreshould 设置新生代对象经过多少次可以晋升到老年代,默认是15次
典型配置案例
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
public static void show01(){
System.out.println("****** hello GC ******");
// byte[] byteArr = new byte[50*1024*1024]; try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
修改前的参数
-XX:InitialHeapSize=265650752
-XX:MaxHeapSize=4250412032
-XX:+PrintCommandLineFlags
-XX:+PrintGCDetails
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC
修改后的参数
-XX:InitialHeapSize=134217728
-XX:MaxHeapSize=4294967296
-XX:MetaspaceSize=536870912
-XX:+PrintCommandLineFlags
-XX:+PrintGCDetails
-XX:ThreadStackSize=1024
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseSerialGC
打印的GC的日志收集信息 规律: [名称: GC前内存占用 -> GC后内存占用(该区内存总大小)]
配置: -Xms10m -Xmx10m -XX:+PrintGCDetails
GC
[PSYoungGen: 1366K->496K(2560K)] 1366K->520K(9728K), 0.0005838 secs][Times: user=0.00 sys=0.00, real=0.00 secs]
YoungGC前新生代占用 YoungGC前堆内存占用 YoungGC耗时 YoungGC用户耗时 系统耗时 实际耗时
YoungGC后新生代占用 YoungGC后堆内存占用
新生代总大小 JVM堆总大小
FULL GC
[PSYoungGen: 464K->0K(2560K)] [ParOldGen: 24K->357K(7168K)] 488K->357K(9728K), [Metaspace: 3075K->3075K(1056768K)], 0.0030993 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Young区 GC前Young区内存占用 Old区 GC前Old区内存占用 GC前堆内存占用 元空间 GC前内存占用 GC耗时 用户时间 系统时间 实际时间
GC后Young区内存占用 GC后Old区内存占用 GC后堆内存占用 GC后内存占用
Young区总大小 Old总大小 JVM堆总大小 元空间总大小
四.常用的GC算法有哪些?可否谈一谈都有垃圾回收器/垃圾回收算法?分别适用于哪些场景?可否详细的说一下为什么从1.10开始都默认采用G1收集器,谈谈你的见解?
1.常用的GC算法有4种
引用计数/复制/标记整理/标记清除
2.有4种垃圾收集器
1.串行垃圾收集器
它是单线程环境设计且只使用一个进程进行垃圾回收,会暂停所有的用户线程,所以不适合高并发,快速响应的服务器环境
2.并行垃圾收集器
多个垃圾线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理平台等弱交互场景使用
3.并发垃圾收集器
用户线程和垃圾回收线程同时进行,可交互执行,不需要暂停用户线程,互联网公司使用的较多,可以满足对交互时间有需求的场景
4.G1垃圾收集器
将堆内存分割为不同的区域,然后并发的对其进行垃圾回收操作
3.垃圾回收器的类型/垃圾回收算法(垃圾收器就是具体实现这些GC算法并实现内存回收,不同版本,不同厂商的虚拟机实现的差别很大)
垃圾回收器
1.UseSerialGC
2.UseParNewGC
3.UseParallelGC
4.UseConcMarkSweepGC
5.UseParallelOldGC
6.UseG1GC
垃圾收集算法
1.SerialCopying (Young区)
2.ParallelScavenge(Young区)
3.ParNew(Young区)
4.SerialMSC(Old区)
5.ParallelCompacting(Old区)
6.CMS(Old区)
7.G1(Young&Old)
年轻代(Young区)
串行GC (Serial Copying)
串行GC是最古老,稳定,高效的收集器,只使用一个线程去回收,但其在进行垃圾回收的过程中可能会产生较长的停顿(SWT)状态,虽然
在收集垃圾的过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定的单个CPU来说,没有线程交互的开销可以获得最高的单线程
垃圾收集的效率,因此Serial垃圾收集器依然是Java虚拟机在Client模式下默认的新生代收集器
配置: -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGC
开启后会使用Serial + Serial Old的收集组合,表示新生代,老年代都会使用串行垃圾收集器,新生代使用复制算法,老年代使用标记整理算法
并行GC(ParNew)
使用多线程进行垃圾回收,在垃圾收集时会暂停其他所有的工作线程直到垃圾收集结束.ParNew实际上就是Serial收集器在新生代多线程
版本,最常见的使用场景是配合老年代的CMS工作,其余的和Serial收集器完全一样.ParNew在垃圾收集过程中同样要暂停其他所有的工作线程
,它是很多Java虚拟机在Server的默认垃圾收集器
配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGC
开启后会使用ParNew + CMS +Serial Old(备份)的组合,新生代使用复制算法,老年代使用标记整理算法,ParNew+Tenured这样的组合在Java8以后不再被推荐
并行GC(Parallel)/(ParallelScavenge)
ParallelScavenge类似于ParNew也是一个新生代垃圾收集器,也是一个基于多线程的垃圾收集器,它是串行收集器正在新生代和老年代的并行化,可以控制
吞吐量,高吞吐量意味着高效的利用CPU的时间,多用于后台计算而不需要太多交互的任务.自适应调节策略也是ParallelScavenge收集器和ParNew收集器的一
个很大的区别,JVM会根据当前系统运行的情况收集性能监控信息,动态调整这些参数以提供最适合的停顿时间或最大吞吐量(-XX:MaxGCPauseMillis)
配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGC
开启后新生代使用复制算法,老年代使用标记整理算法
老年代
CMS收集器(并发标记清除)
是以一种最短回收时间为目标的收集器,适用于大型的B/S系统的服务器上,重视服务器的响应速度,希望系统的停顿时间最短,适用于堆内存大,CPU核数较多 的服务端应用,也是G1出现之前大型应用首选的垃圾收集器.由于耗时最长的并发标记和并发清除的过程中,垃圾收集线程可以和用户线程一起工作,所以整体来看
CMS收集器的内存回收和用户的工作线程是并发执行的.
配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC
开启后将自动打开-XX:+UseParNewGC,使用ParNew(Young区) + CMS(区) + Serial Old的收集器组合,Serial Old作为CMS出错的后备收集器
收集步骤:
1.初始标记 只是标记一下GC Root能直接关联的对象,速度很快,但任然需要暂停所有的工作线程
2.并发标记 进行GC Roots跟踪过程,和用户的线程一起,不需要暂停工作线程,主要标记过程,标记全部对象
3.重新标记 为了修改并发标记的时间,因用户进程继续运行而导致标记产生变动的那一部分对象的标记记录,任然需要暂停所有的工作线程,由于在
并发标记时,用户线程依然运行.因此在正式清理前,再做修正
4.并发清除 清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程,基于标记的结果,直接清理对象
优缺点:
优点: 并发收集低停顿
缺点: 并发执行,对CPU压力大(由于并发进行,CMS在收集与应用线程会同时增加对堆内存的占用,也就是CMS必须要在老年代堆内存用尽之前完成垃圾
回收,否则CMS回收失败时,触发担保机制,串行老年代收集器将会以SWT的方式进行一次GC,从而造成较大的停顿)
采用标记清除算法会造成大量的碎片(标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽最后不得不通过担保机制对堆内
存进行压缩,CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认为0,每次都进行内存整理)来指定多少次CMS
收集后,进行一次压缩的Full GC)
Serial Old收集器
Serial Old是Serial垃圾收集器的老年代版本,是个单线程的收集器,使用标记整理算法,这个收集器也是主要运行在Client的默认的Java虚拟机的老年代垃圾
收集器,现在在JDK1.8之后,已经不推荐使用了.
配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialOldGC
垃圾收集器组合
1.单CPU或小内存,单机程序 -XX:+UseSerialGC
2.多CPU追求最大吞吐量,如计算后台的应用, -XX:+UseParallelGC / -XX:+UseParallelOldGC
3.多CPU追求低停顿时间,需要快速响应,如互联网应用 -XX:+UseConcMarkSweepGC / -XX:+UseParNewGC
参数 | 新生代垃圾收集器 | 新生代算法 | 老年代垃圾收集器 | 老年代算法 |
-XX:UseSerialGC | SerialGC | 复制 | SerialOldGC | 标整 |
-XX:+UseParNewGC | ParNew | 复制 | SerialOldGC | 标整 |
-XX:+UseParallelGC | Parallel[Scavenge] | 复制 | ParallelOldGC | 标整 |
-XX:+UseParallelOldGC | 同上 | 同上 | 同上 | 同上 |
-XX:+UseConcMarkSweepGC | ParNew | 复制 |
CMS+Serial Old收集器的组合 Serial Old作为CMS出错的后备 |
标清 |
-XX:+UseG1GC | 标清 | 标清 | 标清 | 标清 |
G1垃圾收集器
1.以往垃圾收集器的特点
1.年轻代和老年代必须是各自独立且连续的内存块
2.年轻代收集使用单eden+S0+S进行复制算法
3.老年代收集必须扫描整个老年区
4.都已尽可能少而快速地执行GC为设计原则
2.G1收集器的特点
G1是一种面向服务端的垃圾收集器,应用在多处理器和大内存容量的环境中,在实现高吞吐量的同时尽可能满足垃圾收集器暂停时间的特性,此外
还具有如下需求:
1.和CMS一样可以和应用程序并发执行
2.整理空闲空间速度更快
3.需要更多的时间来预测GC的停顿时间
4.不希望牺牲大量的吞吐性能
5.不需要更大的Java Heap
3.为什么使用G1收集器
1.G1能够充分利用多CPU,多核环境硬件优势,尽量缩短SWT
2.G1整体上采用标记-整理算法,剧不是通过复制算法,不会产生内存碎片
3.宏观上看G1之中不在区分年轻代和老年代.把内存划分成独立的子区域Region,可近似理解为围棋棋盘
4.G1收集器将整个的内存区域都混在了一起,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离,
而是一部分Region的集合不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域
5.G1虽然也是分代收集器,但整个内存区域不存在物理上的年轻代和老年代的区别,也不需要完全独立的survivor堆做复制准备.G1只有逻辑上的分代
概念,或者说每个分区可能随G1的运行在不同代之间前后切换最大的好处是化整为零,避免了全内存的扫描,只需要按照区域进行扫描即可
4.G1的算法原理
1.G1将堆划分为若干个区域,仍然属于分代收集器,这些Region一部分包含新生代,新生代的垃圾收集依然采用暂停所有线程的方式将存活的对象拷贝到
老年代或者Survivor区.
2.这些Region中一部分包含老年代,G1收集器通过将对象从一个区域复制到另一个区域完成清理工作,这也意味着,在正常的清理过程中,G1完成了堆的
压缩,这样就不再会有CMS内存碎片的问题了
3.在G1中还有一种特殊的区域,称为Humongous区,如果一个对象的空间超过了分区容量的50%,G1收集器就认为这是一个巨型的对象,这些巨型对象会
直接的分配在老年代,但如果是一个短期存在的巨型对象,就会对垃圾收集器造成负面的影响,为了解决这类问题,G1专门划分了一块Humongous区域专门
用来存放巨型对象,如果H区放不下一个巨型对象,G1就会寻找连续的H区来存储,为了能够找到联系的H区,有时不得不启用Full GC
5.回收过程
1.Eden区数据转移到Survivor区,假如Survivor区的内存不够,Eden区会晋升到Old区
2.Survivor区域的数据会移动到新的Survivor区,部分数据会晋升到Old区
3.最后Eden区回收完毕,GC结束,用户的进程继续执行
6.G1常用的配置参数
1.-XX:+UseG1GC 使用G1垃圾收集器
2.-XX:G1HeapRegionSize = n 设置G1区域的大小,值为2的指数幂,范围是1~32MB,目标是根据Java堆的大小划分出2048个区域
3.-XX:MaxGCPauseMillis = n 最大GC的停顿时间,这是个软目标,JVM尽可能停顿小于这个时间
4.-XX:InitiatingHeapOccupancyPercent = n 堆占用多少就触发GC,一般是45%
5.-XX:ConcGCThread = n 并发GC使用的线程数
6.-XX:G1ReservePercent = n 设置做为空闲空间的预留内存百分比,以降低目标发生内存溢出的风险,默认值是10%,一般不改
7.和CMS相比有哪些优势
1.G1不会产生内存碎片
2.可以精确的控制停顿,该收集器把整个堆划分成固定大小的区域,每次会根据允许停顿的时间去收集垃圾最多的区域
G1收集器的目标是取代CMS收集器,它同CMS相比,在以下方面更具有优势,主要应用在多CPU和大内存服务器环境下,极大的减少垃圾收集的停顿时间,全面提升
服务器的性能,逐步替代CMS收集器.主要改变的是Eden,Survivor和Tenured等区域不再是连续的了,而是变成一个个大小一样的Region,每个Region从1M到32M
不等.一个Region可能属于Eden,Survivor,Tenured任意的内存区域.这样即不会产生内存碎片,同时垃圾收集时间上添加了预测机制,用户可以指定希望的停顿时间.
8.配置
1.生产系统配置 -XX:+UseG1GC -Xms32G -XX:MaxGCPauseMillis=100
2.Spring boot微服务 java -server -Xms1024 -Xmx1024m -XX:+UseG1GC -jar 需要的微服务名称