一、JVM的垃圾回收算法 GC(Garbage Collector)垃圾回收器
-
1.复制算法(新生代垃圾回收算法)
- ①概述:把新生代内存划分为两块内存区域,然后只使用其中一块内存,待那块内存快满的时候,就把
里面的存活对象一次性转移到另外一块内存区域,保证没有内存碎片,接着一次性回收原来的那块内存
区域,再次空出来一块内存区域,两块内存区域重复循环使用 - ②触发时机:分配新对象时,新生代内存空间不足
- ③缺点:对内存的使用率太低(只有一半内存可以用)
- ④优化:1个Eden区(80%),两个Survivor区(10%)
新生对象放到Eden区,垃圾回收时把存活对象放到Survivor区,三区轮转
- ①概述:把新生代内存划分为两块内存区域,然后只使用其中一块内存,待那块内存快满的时候,就把
-
2.标记整理算法(老年代垃圾回收算法)
- ①触发时机:
- 1)在Minor GC之前,检查老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC
- 2)开启空间担保参数,老年代可用内存小于历代新生代GC后进入老年代的平均对象大小
- 3)在Minor GC之后,存活对象大于Survivor,就会进入老年代,此时老年代内存不足。
- 4)–XX:CMSInitiationOccupancyFaction 参数超标
- ②概述:标记出来老年代当前存活的对象,挪到一边,紧凑靠在一起,然后再一次性把垃圾对象都回收掉
- ①触发时机:
-
3.小结:
- 所谓JVM优化,就是尽可能让对象都在新生代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁
- 对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。
- 即不断优化垃圾回收器的机制和算法,降低垃圾回收
-
4.优化方案:
- ①调整新生代的内存比例,避免新生代对象直接进入老年代
- ②调整-XX:SurvivorRatio=8,降低Eden区的比例,给Survivor区更多的空间
二、JVM的痛点:Stop the World
- 1.概述:在垃圾回收的时候,JVM在后台直接进入Stop the World状态,即直接停止我们写的Java系统的所有工作线程
三、垃圾回收器
1.ParNew(新生代垃圾回收器)
- ①优点:多线程垃圾回收机制 (Serial垃圾回收期是单线程)
- 垃圾回收会把系统程序的工作线程全部停掉,用多个垃圾回收线程回收垃圾。
- 1.1.如何为线上系统指定使用ParNew垃圾回收器?
- ①IDEA中可以设置Debug JVM Arguments
- ②使用java -jar命令启动直接在后面接JVM参数即可
- ③部署到Tomcat时可以在catalina.sh中设置JVM参数
- 参数:-XX:+UserParNewGC
- 默认垃圾回收线程数:与CPU核数一致 可以通过-XX:ParallelGCThreads调节
- ①优点:多线程垃圾回收机制 (Serial垃圾回收期是单线程)
2.CMS(老年代垃圾回收器)
concurrent mark-sweep- ①概述:垃圾回收线程和系统工作线程尽量同时执行的模式处理
- ②步骤:
- 1)初始标记
Stop 标记出来所有GC Roots直接引用的对象
补充:方法的局部变量和类的静态变量是GC Roots,但是类的实例变量不是GC Roots - 2)并发标记
- ①对已有对象进行GC Roots追踪
- ②跟系统程序并发运行
- 3)重新标记
- Stop 重新标记第二阶段里新创建的对象和一些失去引用的垃圾
- 4)并发清理(跟系统并发运行)
- 1)初始标记
- 2.1.CMS并发回收机制的缺点:
- ①消耗CPU资源(CMS默认启动垃圾回收线程数量是(CPU核数)+3/4)
- ②Concurrent Mode Failure问题
- 1)问题:并发清理阶段,系统一直运行,可能让一些对象进入老年代,同时变成垃圾对象,即“浮动对象”
- 2)解决:调整CMS垃圾回收触发时机中当老年代内存达到一定比例,自动执行GC
–XX:CMSInitiationOccupancyFaction jdk1.6默认是92% - 3)异常:发生Concurrent Mode Failure会调用Serial Old 垃圾回收器代替CMS,直接Stop
- 2.2.内存碎片问题
- ①碎片整理:-XX:+UseCMSCompactAtFullCollection (默认开启)
- Full GC之后要进行Stop,碎片整理挪到一起
- ①碎片整理:-XX:+UseCMSCompactAtFullCollection (默认开启)
- 3.老年代Full GC比新生代Minor GC慢的原因
- ①新生代直接从GC Roots出发追踪哪些对象是活的,存活对象少,速度快。一次性回收Eden区和Survivor区
- ②CMS的Full GC在并发标记阶段,追踪所有存活对象,老年代存活对象多,速度慢。
- 并发清理阶段,不是一次性回收,零散回收,速度慢
- 内存整理,Stop。引发Concurrent Mode Failure需要用Serial Old
四、内存分配
1.-Xms3072M -Xmx3072M -Xmn1536M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M
五、G1垃圾回收器
-
1.核心设计思路:
- 通过把内存拆分为大量小Region,以及追踪每个Region中可以回收的对象大小和预估时间
- 在垃圾回收的时候,尽量把垃圾回收对系统造成的影响控制在指定时间范围内,同时在有限时间内尽量回收尽可能多的垃圾对象。
-
2.特点:
- ①把Java堆内存拆分为多个大小相等的Region
- ②Region可能属于新生代也可能属于老年代
- ③设置一个垃圾回收的预期停顿时间(Stop the World即系统停顿时间)
- 1)如何对垃圾回收导致系统停顿可控?
- :追踪每个Region的回收价值(回收时间及垃圾大小)
- 1)如何对垃圾回收导致系统停顿可控?
-
3.G1对应的内存分配
- ①使用 -XX:+UseG1GC 指定G1垃圾回收器,会用堆内存大小除以2048(JVM最多有2048个Region)
- ②开始 默认新生代对堆内存的占比是5%,可通过 -XX:G1NewSizePercent 设置
- ③运行 JVM不停给新生代增加Region,新生代占比最多不超过60%。 -XX:G1MaxNewSizePercent
- ④G1新生代还有Eden和Survivor概念,占据不同的Region
-
4.G1的新生代垃圾回收
- ①相同:与ParNew垃圾回收类似,Eden区满进行回收
- ②不同:G1可以设定目标GC停顿时间 -XX:MaxGCPauseMills 默认200ms
- ③新生代进入老年代:
- 1)对象躲过多次垃圾回收,达到一定年龄 -XX:MaxTenuringThreshold
- 2)动态年龄判断,即新生代GC过后,存活对象超过Survivor的50%
-
5.大对象Region
- ①判定:超过一个Region大小的50%
- ②触发时机:新生代、老年代回收时,顺带带着大对象Region一起回收
-
6.新生代+老年代 垃圾混合回收(Mixed GC)
-
①触发时机: 老年代占据堆内存的45%的Region时
-XX:InitiatingHeapOccupancyPercen -
②回收过程:
- 1)初始标记 Stop 仅标记GC Roots直接能引用的对象
- 2)并发标记 GC Roots追踪
- 3)最终标记 Stop
- 4)混合回收 Stop
- 注意:此时回收老年代、新生代和大对象(根据GC停顿时间挑选部分Region)
最后一个阶段可执行多次混合回收 -XX:G1MixedGCCountTarget
- 注意:此时回收老年代、新生代和大对象(根据GC停顿时间挑选部分Region)
-
③G1整体是基于复制算法进行Region垃圾回收的
- -XX:G1HeapWasterPercent 5%(空闲Region数量达到堆内存的5%,停止混合回收)
- -XX:G1MixedGCLiveThresholdPercent 85%(存活对象低于85%的Region才回收)
-
-
7.新生代GC优化
- ①给JVM的堆区域足够的内存
- ②合理设置 -XX:MaxGCPauseMills 参数
-
8.Mixed GC优化
- ①核心:尽量避免对象过快进入老年代,避免频繁触发mixed gc
-
9.G1非常适合内存很大的机器
- 因为内存很大,如果不用G1,会导致新生代每次GC回收垃圾太多,停顿时间太长,G1可以指定每次停顿时间
-
10.如何打印出JVM GC日志?*************
-XX:+PrintGCDetails
打印详细的gc日志-XX:+PrintGCTimeStamps
打印每次GC发生的时间-Xloggc:gc.log
将gc日志写入一个磁盘文件
-
11.开发工具指定JVM参数运行程序
- 添加VM arguments
在目录下生成gc.log
- 添加VM arguments
jdk自带命令行工具
六、jstat
- 1.使用:
jstat -gc PID 对应pid的java进程的内存和GC情况 - 2.其他命令
jstat -gccapacity PId: 堆内存分析
jstat -gcnew PID:年轻代GC分析
七、jmap
- 1.jmap -heap PID 堆内存相关
- 2.jmap -histo PID 对象占用内存大小降序
- 3.jmap -dump:live,format=b,file=dump.hprof PID 生成堆内存转储快照
八、jhat
- jhat内置web服务器,支持通过浏览器以图形化方式分析堆转储快照
- 1.jhat dump.hprof -port 7000
九、对线上系统进行JVM监控
- 1.方法一:每天在高峰和低峰期使用jstat、jmap、jhat等工具查看
- 2.监控系统:Zabbix、OpenFalcon、Ganglia
十、内存溢出
-
1.Metaspace区域的内存溢出
- ①设置大小:-XX:MetaspaceSize -XX:MaxMetaspace
- ②原因:
- 1)上线系统对Metaspace区域使用默认参数,内存过小
- 2)使用动态代理技术生成一些类,导致类过多
-
2.JVM栈内存溢出
- ①一个线程调用多个方法的入栈和出栈
- ②每次方法调用的栈帧都是占用内存的
- ③原因:递归调用方法
-
3.堆内存溢出
- ①原因:有限的内存放过多对象,且大多数是存活的。
- ②场景:
- 1)系统承载高并发请求
- 2)系统有内存泄漏的问题
-
4.如何对线上系统的OOM异常进行监控和报警
- ①监控系统
- ②被动等待系统挂掉
-
5.JVM内存溢出时,自动dump一份内存快照
- ①-XX:+HeapDumpOnOutOfMemoryError //OOM时自动dump内存快照
- ②-XX:HeapDumpPath=/usr/local/app/oom 快照位置
-
6.JVM参数模板
“-Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom”
- 补充:
- ①Tomcat本身就是一个JVM进程,tomcat是用java写的
- ②我们写好的类被tomcat加载到内存中,由tomcat执行所写的类
- ③Tomcat有自己的工作线程,几百个
- http-nio-8080-exec-1089 :tomcat的工作线程,负责调用代码运行时堆内存不够
-
7.Jetty服务器的NIO机制导致堆外内存溢出
- ①Direct buffer memory 堆外内存(操作系统直接管理)
- ②Java代码里申请一块堆外内存:
- 使用DirectByteBuffer这个类,构建一个对象在JVM堆内存中,堆外内存会划出来一块内存跟这个对象关联
- ③最终优化:
- 1)合理分配内存,给年轻代更多内存,让Survivor区域有更大的空间,避免DirectByteBuffer对象进入老年代,导致堆外内存释放不掉。
- 2)放开-XX:+DisableExplicitGC限制,让System.gc()生效。
-
8.微服务架构RPC调用引发的OOM
- ①对象序列化和反序列化
- ②序列化失败重新开辟数组保存字节数组(不能过大)