一、怎么判断对象是否存活的算法?
1、引用计数法
- 算法描述:当有一个对象引用我时,会在计数器上+1,当计数器为0是,说明没被对象引用,故需要被垃圾收集器回收
- 缺点:每次对对象赋值时需要维护引用计数器,较难处理循环引用(持有并等待,哲学家思考问题)
2、可达性分析算法
(1)、什么是可达性算法(基本概念)?
- 通过一个根节点RootGC作为一个起点,从这个节点往下搜,搜索走过的路径就是引用链,当一个对象没有引用链的时候,说明根节点到该对象不可达,证明该对象是不可用的。(从根节点往下搜该对象,如果没搜到说明该对象没有被引用了,需要被回收了。)
(2)、Java中可以用作根节点的对象?
- 虚拟机栈中引用的对象
- 方法区中静态变量引用的对象
- 方法区中静态常量引用的对象
- 本地方法栈JNI引用的对象
(3)、一个对象被确认不可达的时候一定会被回收吗?
- 不会,当一个对象被确认不可达的时候,会被标记,并且执行finalize方法,若在该方法中与任何一个对象有关联的话,那就不会被回收,若无关联,将等待被垃圾回收。
二、垃圾回收算法
1、元空间是怎么垃圾回收?
- MetaspaceSize,达到一个阈值就会触发垃圾收集,进行类型卸载。
- GC会对该值进行动态调整,当回收很多空间时,会适当减少该值大小,当释放很少时,会适当增加该值大小,最大不超过MaxMetaspaceSize,若超过则抛出OOM异常。
- 每一个类加载器的存储区域就是一个小块的元空间,众多类加载器组成一个大的元空间,当某个类加载器被标记为不可存活时,就会将其卸载,回收。
2、有哪些算法?(可达性分析区标记)
(1)、复制算法
- 算法描述:年轻代使用,from区(包含eden)复制到to区。因为在该区域,对象存活率低,所以比较适合用,若存活率比较高,则不适合使用,当达到60%会直接放到老年代。
- 原理思想:将内存分为两块,每次只用其中的一块,当这一块内存用完就开始垃圾回收,并且把还存活的数据复制到另一块空的内存上面。
- 优点:不会产生内存碎片
- 缺点:耗空间,空间没有充分利用
(2)、标记清除
- 算法描述:老年代使用,先标记要回收的对象,然后统一回收这些对象。
- 原理思想:先埋点,后清除
- 优点:节约空间,充分利用空间
- 缺点:产生内存碎片,需要先标记后才能清除,速度上会慢点,效率低。
(3)、标记压缩
- 算法描述:先标记,在清除,后压缩整理
- 原理思想:在标记清除的基础上在整理下碎片。
- 优点:节约空间,没有碎片
- 缺点:耗时比较长。
3、FullGC触发条件?
- 代码调用
System.gc
时 - 老年代空间不足
- 方法区空间不足
- 通过MinorGC直接进入老年代,此时老年代没有可用的内存存放该内存
- MinorGC复制算法后,to区不够放,则把该对象转存到老年代,此时老年代没有可用的内存存放该内存
三、垃圾收集器
1、产生Stop-The-World情况
- 新生代和老年代GC时
- 死锁检查
- jstack工具,Dump线程
- jmap工具,堆Dump
2、垃圾回收器有哪些?
1、新生代:Serial、ParNew、Parallel Scavenge
(1)、Serial串行收集器
- 描述:串行执行,单线程收集器,复制算法,可以和Serial Old搭配使用。
- 详解:在工作的时候会停止其他所有的工作线程(Stop-The-World)
- 缺点:效率低
- 参数控制: -XX:+UseSerialGC
(2)、ParNew收集器(常用的)
- 描述:在Serial基础上,支持多线程的,核心数根据服务器CPU核心数来的,可以和CMS搭配使用
- 参数控制:
- -XX:+UseParNewGC,指定年轻代使用该收集器
- -XX:ParallelGCThreads,限制线程数量
(3)、Parallel Scavenge收集器
- 描述:和ParNew收集器一样支持多线程并发执行,与之不同的是该收集器的目标是达到一个可控的吞吐量。吞吐量=程序运行时间/(程序运行时间+GC时间)。和Serial Old配合使用。
- 它是如何保证吞吐量的呢?参数太多了,用到时在研究。
- 参数控制:-XX:+UseParallelGC
- 缺点:
- 一位的追求吞吐量,减少GC的时间,就会到时频繁的进行垃圾回收(因为回收500m肯定比回收2000mGC要短)
- jdk1.5之前老年代用Serial Old还是比较慢的,性能不太好
- Jdk1.6以后老年代可以用Paralled Old收集器,效果会好点
2、老年代:Serial Old、CMS、Paralled Old
(1)、Serial Old收集器
- 描述单线程收集器,标记整理算法
- 补充:如果CMS垃圾回收失败了,会用Serial Old回收
(2)、Paralled Old收集器,
- 描述:多线程并行的收集器,标记整理
- 参数控制:-XX:+UseParallelOldGC
(3)、CMS收集器(常用的)
- 描述:以获取最短回收停顿时间为目标的收集器,标记清除算法
- 工作流程:
- 初始标记:可达性分析,判断对象是否存活,标记为存活的对象,速度很快,需要Stop-The-World
- 并发标记:初始标记完并不会立刻垃圾回收,这期间程序不停的有对象创建,需要可达性判断,标记为存活的对象,耗时长
- 重新标记:修正程序运行期间导致标记产生变动的那部分对象,耗时长, 需要Stop-The-World
- 并发清除:清除标记的对象。
- 优点:对于耗时的操作,是并发执行,低停顿
- 缺点:基于标记清除算法,导致内存碎片过多;多线程执行占用CPU
- 导致并发模式失败的情况(Concurrent Mode Failure)
- 对象提升到老年代的速度太快,垃圾收集赶不上它的速度,老年代的空间不足
- 老年代碎片化太严重,一个大的对象放不下
- 浮动垃圾:并发清除期间,用户线程还在运行,这期间产生的对象内存,即为浮动垃圾
- 参数调优:
- -XX:+UseCMSCompactAtFullCollection(默认开启的):由于CMS是基于标记清除算法,会产生会多内存碎片,通过该参数,标记清除完之后再整理内存碎片,该过程无法并发,会占用时间,到时停顿时间变长。
- -XX:+CMSFullGCsBeforeCompaction:执行多少次不压缩的FullGC,以后再整理,默认是0,每次都整理
- -XX:UseConbcMarkSweepGC:使用CMS垃圾回收器
- -XX:+UseCMSCompactAtFullCollection(默认开启的):由于CMS是基于标记清除算法,会产生会多内存碎片,通过该参数,标记清除完之后再整理内存碎片,该过程无法并发,会占用时间,到时停顿时间变长。
3、补充G1、ZGC()目前JDK11实现性的收集器
(1)、G1收集器(新生代和老年代):
- 适用场景:
- 多核CPU、JVM内存占用比较大的(至少大于4G)
- 为什么?因为在运行过程中会产生大量的内存碎片,需要经常压缩空间。
- 需要可控的,可预期的GC停顿周期。
- 多核CPU、JVM内存占用比较大的(至少大于4G)
- 回收方式
- YoungGC,根据预期停顿时间,选定年轻代的部分的Region进行回收,以此来控制时间开销
- MixedGC,选定所有年轻代Region和根据global concurrent marking找到部分老年代Region进行回收
- 注意:MixedGC不是FullGC,只会回收部分来年代Region,如果MixedGC是在无法跟上程序分配内存的速度,则会使用Serial Old GC(FullGC)来收集老年代垃圾,故G1是不提供FullGC的。
- global concurrent marking工作整体过程类似CMS,初始标记、并发标记、最终标记、清理垃圾
(2)、ZGC收集器(不分代)
- 目标
- 支持TB级堆内存(最大16T)
- 最大GC停顿10ms
- ZGC为什么很快?
- 染色指针,是否需要垃圾回收放到对象头中
- 读屏障+CAS
- 使用NUMA架构技术高效分配空间和进行对象的扫描
4、小总结
-
新生代:
- Serial(jdk5之前发布,单线程复制算法)
- ParNew(多线程复制算法)
- Parallel Scavenge(jdk4发布,多线程标记复制算法,可控的吞吐量=运行代码/(运行代码+垃圾收集时间))
-
老年代:
- Serial Old(jdk5之前发布,单线程标记-整理)
- Concurrent Mark Sweep(CMS jdk5发布,并发低停顿收集器,标记-清除算法,标记-整理(多少次FULLGC以后整理碎片|分配对象内存空间不够直接FULLGC))
- Paralled Old(jdk6发布,多线程标记-整理)
-
全堆收集器:
- Garbage First(G1,jdk1.7.4出现,9时官方推荐)
-
搭配关系:
- Serial&Serial Old(jdk5之前)
- ParNew&Serial Old(jdk5之前)
- Parallel Scavenge&Serial Old(jdk5之前,浪费性能)
- Parallel Scavenge&Paralled Old(jdk5之后,吞吐量优先)
- Serial&CMS(jdk5~8,jdk9取消)
- ParNew&CMS(jdk5~8官方推荐,9之后合并)
-
都遵循分代收集理论,G1以前分新生代和老年代,G1全功能收集器,基于Region的堆内存布局,G1优先处理回收价值收益最大的那些Region
-
jdk9以后:G2(推荐),取消ParNew&Serial Old、取消Serial&CMS、此后ParNew合并CMS两者不可以再和其他收集器搭配使用
-
全功能收集器:G1(局部看两个Region标记-复制、整体看标记-整理)、ZGC(低延迟垃圾收集器)
-
选型:目前在小内存应用上CMS的表现大概率仍然要会优于G1,而在大内存应用上G1则大多能发挥其优势,这个优劣势的Java堆容量平衡点通常在6GB至8GB之间
四、内存分配策略
1、对象优先在TLAB/Eden分配?
- TLAB是本地线程分配的缓冲区,在Eden中独立开出一小块空间(1%)作为TLAB,若对象可以在TLAB上分配则分配,若不能则分配在Eden中,TLAB线程私有的,不存在共享资源竞争的情况
2、什么情况直接进入老年代?
- 大对象,年轻代的空间经过YoungGC后不够存放时
- 长期存活的对象,活过了GC分代年龄(最大15次,对象头4字节存放GC分代年龄)
3、什么是空间分配担保?
- 新生代MinorGC之前,会检查老年代最大可用内存大小是否大于本次新生代的对象大小,若大于则进行MinorGC,若不大于则先进行FullGC
五、常见的JVM参数
…
六、对象的引用
1、强引用
- 概念:当内存不足时,JVM抛出oom,也不会回收它。
- 示例:new出来的对象是强引用,是使用最普遍的引用。
2、软引用
- 概念:当内存足够时,JVM不会回收它,当内存不够时,再回收。
- 应用:
- 实现内存敏感的高速缓存。
- 可以和引用队列联合使用,如果软引用的对象被垃圾回收了,JVM会把软引用加入到与之关联的引用队列中。SoftReference sf = new SoftReference(obj);
3、弱引用
- 概念:垃圾回收器扫描到弱引用对象,不管当前内存是否足够,都会清除。(由于垃圾回收器是一个优先级很低的线程,因此不会那么快发现到)
- 应用:
- 可以和引用队列联合使用,如果弱引用的对象被垃圾回收了,JVM会把软引用加入到与之关联的引用队列中。WeakReference wf = new WeakReference(obj);
4、虚引用
- 概念:不会决定对象的生命周期,在任何时候都会被垃圾回收。
- 应用:
- 必须和引用队列联合使用。当垃圾回收准备回收一个对象时,如果发现它还有虚引用,就会在回收对象内存之前,把这个虚引用加入到与之关联的引用队列中。PhantomReference pf = new PhantomReference(obj);
- 由于跟踪垃圾回收的活动,一般利用虚引用感知到jvm垃圾回收动作,然后就可以回收堆外内存
七、常见的OOM以及原因?
1、堆溢出
- 错误:oom:Java heap space
- 本质:FullGC之后空间还是不足,则报错
- 原因:
- 应用程序保存了太多无法被GC回收的对象,导致无法在堆中分配新的对象
- 应用程序过度使用finalizer,导致要回收对象没发回收(在该方法中,该对象又引用了其他对象)。
- 解决:
- 检查代码是否有递归、大循环、死循环在疯狂new对象。
- jmap等监控工具
- 估算堆大小,在机器支持的情况下,适当调整堆大小
2、栈溢出
- 错误:
- StackOverflowError
- oom:如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时,会抛oom
- 原因:
- 单个线程下,栈帧太大,或者栈容量太小
- 不断的创建线程,导致oom
- 解决:
- StackOverflowError错误,则检查是否存在递归调用
- oom,检查是否有哪里疯狂创建线程
- 在机器支持的情况下,适当调整栈大小
3、方法区
- 解释:
- 永久代存放class信息、静态常量、静态变量,元空间存放calss信息。
- 本质:当运行时产生大量的class时,可能会导致oom
- 原因:
- 使用cglib生成了大量的代理类
- jdk7以前,频繁的错误使用String.intern方法
- 大量的jsp或者动态产生大量的jsp
- 解决:
- 重启,增大内存。
八、JVM常用监控命令
1、jps
- 进程相关
2、jstat
- 监控虚拟机统计信息
3、jstack
- 注重监控线程情况
4、jmap
- 监控整体内存情况,可以把文件dump下来,用分析工具去分析
5、jinfo
- java配置信息工具
九、CPU占用过高问题排查
1、查看系统状况
- top命令查看CPU内存使用情况
2、定位问题的线程
- ps -mp pid -o THREAD,tid,time
3、查看问题线程的堆栈1
- jstack pid | grep threadId
4、jstat查看进程内存情况
- jstat -gcutil pid 2000 10(没2s一次,共计10次)
5、jstack和jmap分析进程堆栈和内存情况
- 使用jmap命令dump文件,拿到本地使用mat、jprofler等工具分析
jmap -dump:format=b,file=dump.bin 6764
jstack -l 6764 >> jstack.out