一.如何判断一个对象是否应该被回收?
- 引用计数法:给对象增加一个引用计数器,当对象被引用就加1,当失效的时候就减1,应用计数为0的时候就可以被回收
缺点:不准确,两个对象如果存在循环依赖那永远都不会为0,就不能被回收
- 可达性分析:通过一系列的GC Roots的对象作为起始点,从这些根节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的
二.哪些对象可以作为GC Roots呢?
- *虚拟机栈中的引用对性
- *方法区中类静态属性引用的对象
- *方法区中常量所引用的对象
- *本地方法栈中Native中引用的对象
- 虚拟机内部的引用对象(类记载器、基本数据对应的Class对象,异常对象)。
- 所有被同步锁(Synchronnized)持有的对象。
- 描述虚拟机内部情况的对象(如 JMXBean、JVMTI中注册的回调、本地缓存代码)。
- 垃圾搜集器所引用的对象。
三.Java中有几种引用?
- 强引用: 这种对象只要有引用就不会被回收;
Object object =new Object()`
- 软引用:如果一个对象只具有软引用,在内存空间足够的时候,垃圾回收器是不会回收它的;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
SoftReference<MyObject> myObjectSoftReference = new SoftReference<>(new MyObject());
- 弱引用:弱引用只要在GC的时候就会被回收,案例:ThreadLocal
WeakReference<String> weakRef = new WeakReference<String>(str); // 弱引用
- 虚引用:虚引用与其他几种引用都不同,它并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。可以用来作为GC回收Object的标志。
四.能够找到 Reference Chain 的对象,就一定会存活么?
- JVM判断对象是否是垃圾采用的是可达性分析算法,通过 GC Roots 来判定对象存活,从GC Roots向下追溯、搜索,会产生一个叫做 Reference Chain 的链条,但是能够找到 Reference Chain 的对象却不一定会存活,还得考虑到对象的引用类型,比如如果对象是软引用类型,那么在堆内存不足时,该对象就会在GC时被回收,而如果对象是弱引用类型,那么只要发生了GC,该对象就会被回收。
五.垃圾回收算法有哪几种?及优缺点?
- 标记清除:分为两个阶段标记阶段 和 清除阶段,在标记阶段首先通过根节点寻找被引用的对像进行标记,在第二阶段会清楚未被标记的对象;
- 缺点:产生空间的不连续;
- 步骤:一次标记,一次清除, 两次扫描;
- 场景:老年代
- 复制算法:从根集合节点进行扫描,标记出所有存活的对象,并且将存活的对象复制移动到另一块空白内存中,然后将另一块未移动的对象清楚;
- 缺点:浪费空间,必须保证有一块空间是空白的,并且需要复制移动,比较慢;
- 步骤:一次标记伴随着复制和移动;
- 场景:年轻代,朝生夕死的对象;
- 标记整理:是标记清除的升级,保证了空间连续行,从根节点找到所有引用的对象,并将其压缩,进行整理,将存活的对象放在另一端,清除另一片区域;
- 缺点:在整理过程中要进行移动,吞吐量会减少,停顿时间会增加;
- 步骤:一次标记,一次整理,一次清除;
- 场景:老年代;
- 分代算法:老年代和年轻代分别使用不同的回收算法,叫做分代算法;
六.YoungGC,OldGC,FullGC,Mixed GC产生的情况及原因,带来的危害,如何避免?
-
YoungGC:
- 什么时候会YoungGC:
Eden区空间不足时,触发YoungGC;Eden区占了新生代的80%,大部分对象都是“”朝生夕死“的,因此YoungGC会比较频繁,YoungGC会引发STW,暂停其他用户线程,等GC结束后,用户线程才恢复;
- 堆中TLAB:
如果我们开启了-XX:UseTLAB,JVM会优先在TLAB区域上进行分配,这是线程私有的区域,默认情况下TLAB的空间占Eden区域的1%,也可以通过配置属性配置占比大小(-XX:TLABwasteTargetPerence), 如果分配失败,JVM会通过使用加锁的方式在Eden区进行分配;
- 逃逸分析:
不是所有的对象都会分配到堆中,在JDK6u23版本后就默认开启了逃逸分析,通过逃逸分析的方法,一个对象如果没有逃逸出方法的话(对象只在一个方法中使用),那么就可能优化成栈上分配,随着方法执行的结束,栈空间就被移除;
- 什么情况会直接分配到老年代:
新生区的对象默认生命周期超过 15 ,将进入老年代,设置 JVM 参数:-XX:MaxTenuringThreshold=N 进行设置; 大对象 大数组:所谓的大对象是指需要大量连续内存空间的java对象,最典型的大对象就是那种很长的字符串以及数组,大对象对虚拟机的内存分配就是坏消息,尤其是一些朝生夕灭的短命大对象,写程序时应避免。 空间分配担保:在一次安全Minor GC 中,存活的对象不能在另一个Survivor 完全容纳,则会通过担保机制进入老年代 动态年龄判断:判断s区的相同年龄的对象的大小总和s区内存空间的一半,那么大于这个你年龄的对象会放到老年代;
-
OldGC
-
- 什么时候会OldGC:
- 老年代空间不足时,触发MajorGC;目前只有CMS GC会单独回收老年代的行为,很多时候MajorGC与FullGC混淆使用,区别在于是老年代的回收 还是整堆回收;MajorGC的速度一般会比YoungGC慢10倍以上,STW的时间更长
-
FULLGC
-
- 什么时候会FULLGC:
- 调用System.gc()
- 老年代空间不足
- 方法区空间不足
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
- 由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
-
MixedGC
- 什么时候会YoungGC:
Mixed GC是G1中特有的概念,当老年代内存占据到了45%就户触发Mixed GC,对新生代和老年代都进行回收
七.有哪几种垃圾回收器?
- 年轻代:复制算法
- Serial 串行垃圾回收器
- Parallel并行垃圾回收器
- ParNew 并行垃圾回收器
- 老年代:标记整理 或 标记清除
- Serial Old 串行老年代垃圾器
- Parallel Old 老年代的并行垃圾回收器
- CMS 并发
- 整堆:
- G1
- ZGC
八.CMS和G1的介绍及区别?
-
cms是老年代的回收器 G1可以对老年代和新生代分区回收
-
cms是标记-清除算法,会产生空间碎片,浪费空间, G1结合了标记-整理和复制算法 空间是连续的
-
cms属于并发执行 而G1是并发和并行执行
-
cms是低停顿 而G1是可预测停顿可以进行指定时间
-
cms 初始标记 并发标记 重新标记 并发删除标记
-
G1 初始标记 并发标记 最终标记 筛选回收
-
G1
- G1垃圾是1.9以后默认的垃圾回收器
- G1垃圾回收器将整个堆空间分成多个Regoin区(2048个),每个Regoin区的大小在1-32M,会优先回收占用空间比较多的区域;
- 采用的是标记整理算法进行回收;
- 可以在一定程度上避免 Full GC,减少停顿时间。此外,G1 垃圾回收器还支持增量清理和并发清理,能够在保证较短停顿时间的同时,达到很高的吞吐量。
- Region是通过HeapRgion对象来实现的,包含一个连续空间 和一些垃圾回收信息
- 在对HeapRegion进行回收时候,会将存活对象复制到另一个对像,避免碎片
- G1 在进行垃圾回收时,会选择一个或多个收集集合作为本次 GC 的目标,这个选择过程主要涉及到以下因素: 集合中对象的填充度和总共占用空间; 集合的 Region 数量和大小; 集合之间的负载均衡情况;
九.项目中实际用到的垃圾回收器,出于什么考虑?
- CPU核心大于4,内存4-6GB的,使用CMS垃圾收集器: ‐XX:+UseParNewGC -XX:+UseConcMarkSweepGC
- CPU核心大于8,内存6-32GB的,使用G1垃圾收集器: ‐XX:+UseG1GC
十.实际中我们如何调优?
-
什么时候优化:
- 在程序上线前要通过压测进行优化
- 上线后观察:如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化,如果GC时间超过1-3秒,或者频繁GC,则必须优化。
- 业务已上线应用线程缓慢,或者出现OOM现象,或者CPU过高时;
-
调优方向:顺序调整不可逆行为;然后不断的尝试
- 满足内存
- 停顿时间
- 吞吐量
-
调优步骤:
-
增加GC日志的打印参数:-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-default.log;
-
查看GC日志
2022-08-05T13:45:23.336+0800: 本次GC发生时间 4.866: 举例启动应用的时间 [GC【表示GC的类型,youngGC】 (Metadata GC Threshold) 元空间超阈值 [PSYoungGen: 136353K->20975K(405504K年轻代总空间)] 160049K->48437K(720384K)整堆), 0.0092260 secs本次垃圾回收耗时] [Times: user=0.00本次GC消耗CPU的时间 sys=0.02系统暂停时间, real=0.02 secs实际应用暂停时间]2022-08-05T20:24:47.815+0800: 6.955: 刚启动服务就Full GC【整堆回收!!】 [Full GC (Metadata GC Threshold) Metaspace空间超限! [PSYoungGen: 701K->0K(72704K)] 年轻代没有回收空间 [ParOldGen: 38678K->35960K(175104K)] 39380K->35960K(247808K), 老年代也没有到阈值,整堆更没有到阈值 [Metaspace: 56706K->56706K(1099776K)], 0.1921975 secs] [Times: user=1.03本次GC消耗CPU的时间 sys=0.00系统暂停时间, real=0.19 secs实际应用暂停时间]-
定位问题工具GCeasy.io 并下载报告(可视化)
-
分析的内容
- JVM内存占用情况
- 关键性能指标 吞吐量/GC 延迟
- GC 可视化交互聚合结果 (可定位多少毫秒内发生多少次GC)
- GC 统计 GC总次数 young GC full GC次数 以及停顿时间
- GC原因:Metadata GC Threshold**: 元空间超阈值 Allocation Failure :年轻代空间不足
-
-
调优方案:
- 调整垃圾回收器算法:
- 优化内存的管理
- 编译优化
- 调整线程池
- 代码层面优化
十一.工作中常用的调优命令?
-
jps 定位到进程号
-
jstat
- eg:需要每 100 毫秒查询一次进程 13616 垃圾收集状况,一共查询 8 次,那命令应当是: jstat-gc 13616 100 8
- eg:编译统计:
jstat -compiler 19570; - eg:堆内存统计:
jstat -gccapacity 19570;
-
jinfo
- jinfo查看和修改虚拟机的参数。
- jinfo –sysprops 可以查看由 System.getProperties()取得的参数
- jinfo –flag 未被显式指定的参数的系统默认值
- jinfo –flags(注意 s)显示虚拟机的参数
- eg:通过 jinfo 修改参数,打印 GC 详情:
-
jmap 生成dump文件
- jmap -heap pid:查看堆使用情况
- jmap -histo pid:查看堆中对象数量和大小
- jmap -dump:format=b,file=heapdump pid:将内存使用的详细情况输出到文件
-
jstack 一般检查死锁
- 在终端中输入jsp查看当前运行的java程序
- 使用 jstack -l pid 查看线程堆栈信息
- 搜索deadLock
-
top命令查看CUP情况
- 通过top找到cpu多高的pid
- jps -l | grep 进程pid :通过jps -l打印进程pid对应的java程序是哪一个
- ps -mp 进程pid -o THREAD,tid,time:获取CPU占用高的线程TID
- 将上面获取到TID转化成16进制
- jstack [PID] > [fileName].txt 输出该线程的快照