内存区域
方法区/元数据空间:存放各种类相关的信息。线程共享。
堆内存:存放代码创建的各种对象。线程共享。
程序计数器:用来记录当前执行的字节码指令的位置。线程独占。
Java虚拟机栈:保存每个方法内的局部变量等数据的。线程独占。
栈帧:局部变量表,操作数栈,动态链接,方法出口等。调用方法时压栈帧,退出方法出栈。
本地方法栈:native 方法调用的时候,会有线程对应的本地方发展。类似java虚拟机栈。
分代模型
分代的根本原因是,一部分对象存活时间很短,一部分对象存活时间很长。
对象的生存周期不同,因此,JVM将堆内存划分了两个区域,一个是年轻代,一个是老年代。
根据年轻代和老年代的特点不同,采用的GC算法也不同。
年轻代:大部分年轻代的对象都是朝生夕死。
老年代:长时间存活的对象会放到老年代。
永久代:也叫方法区,存放一些类信息。
对象什么时候进入新生代?
大部分正常对象,优先在新生代分配内存。
对象什么情况下回进入老年代?
1.对象年龄大于等于15岁,即在新生代成功躲过15次,还没回收,则会被转移到老年代。
2.动态对象年龄判断:当前存放对象的survivor区,一批对象总大小,大于这块survivor区的内存大小的50%,此时大于等于这批对象年龄的对象,可以直接进入老年代。
3.大对象直接进入老年代:-XX:PretenureSizeThreshold,可以设置字节数,大于这个字节数的对象, 就直接放到老年代里。主要是避免一个很大的对象在内存中来回复制,降低性能。
4.Minor GC 后对象太多无法放入 survivor 区。则会直接都放入老年代。
Young GC / Minor GC 触发时机?
1.新生代,分配内存时,发现新生代eden区内存不足时。
2.G1回收时,预估到本次回收将近达到配置的最大停顿时间时。
老年代垃圾回收触发时机
1.执行Minor GC 后,如果老年代空间不足以保存存活的对象,则进行一次Old GC。
2.Minor GC 前检查,老年代可用的连续内存空间 < 新生代历次Young GC后升入老年代的对象总和平均大小,提前Old GC。
3.使用CMS垃圾回收时,-XX:CMSInitiatingOccupancyFaction 参数可以用来设置老年代占用多少比例的时候触发CMS垃圾回收。
什么情况下会发生Metaspace内存溢出
1.本身代码大于默认配置,或配置过少 2.cglib之类动态生成类过多。
核心JVM参数
- -Xms : Java 初始分配的堆内存大小
- -Xmx:Java 堆内存的最大大小
- -Xmn:Java 堆内存中的新生代大小,减去新生代大小就是老年代大小
- -XX:PermSize: 永久代初始大小(1.8之前)
- -XX:MaxPermSize:永久代最大大小 (1.8之前)
- -XX:MetaspaceSize:永久代初始大小(1.8之前)
- -XX:MaxMetaspaceSize: 永久代最大大小 (1.8之前)
- -Xss:每个线程的栈内存大小
- -XX:+PrintGCDetils:打印详细的gc日志
- -XX:+PrintGCTimeStamps:打印每次GC发生的时间
- -Xloggc:xx-gc.log:指定gc日志磁盘文件
- -XX:+HeapDumpOnOutOfMemoryError:OOM时自动dump内存快照
- -XX:HeapDumpPath=/usr/local/app/oom:内存快照放到哪去
xms 和 xmx 一般配置成一样大小,减少jvm动态调整的性能损耗。 永久代一般设置几百M即可。 栈内存大小一般是512KB到1MB。
GC算法与垃圾回收器
Stop The World 状态
停止ava系统的所有工作线程。只进行垃圾回收的工作。
可达性分析
JVM使用可达性分析算法判断哪些对象可以被回收。
对象逐层向上查找,是否有GC Roots引用。
GC Roots: 1.局部变量 2.静态变量
垃圾回收器
Serial和Serial Old垃圾回收器
- 分别用来回收新生代和老年代的垃圾对象
- 单线程运行,运行时停止其他线程,一般不用
ParNew
- ParNew 一般都是用在新生代
- 多线程并发,默认线程数跟CPU核心数一样
- -XX:+UseParNewGC 开启
- -XX:ParallelGCThreads 线程数配置,一般直接用默认值
CMS垃圾回收器
- 用在老年代
- 多线程并发
- 垃圾回收和系统工作线程,尽量同时执行模式
CMS 执行过程: 1.初始标记
- stop the world
- 标记 GcRoots 直接引用的对象
2.并发标记
- 系统线程可以随意创建各种新对象,继续运行
- 是对老年代所有对象进行GC Roots追踪,是最耗时的,但因为并发所有没有性能影响
3.重新标记
- stop the world
- 重新标记下在第二阶段里变动过的对象(新建,失去引用)
4.并发清理
- 让系统程序随意运行,清理掉之前标记为垃圾的对象
CMS 主要是将耗时短的阶段和耗时长的阶段分离,只有短的阶段才stop the world。
CMS 调优 问题1:占用CPU资源。
CMS默认言动的垃圾回收线程的数量是(CPU核数+3)/4
问题2:Concurrent Mode Failure
-XX:CMSInitiatingOccupancyFaction 参数可以用来设置老年代占用多少比例的时候触发CMS垃圾回收
JDK 1.6 默认 92%
预留8%的空间给并发回收期间,系统程序把一些新对象放入老年代中。
CMS垃圾回收期间,需要放入老年代的对象大于可用空间,会发生Concurrent Mode Failure。然后立即使用SerialOld垃圾回收器,慢慢回收。
问题3:内存碎片
-XX:+UseCMSCompactAtFullCollection ,该参数默认开启,Full GC 之后 stop the world,进行碎片整理。
-XX:CMSFullGCsBeforeCompaction,多少次Full GC之后再执行一次内存碎片整理。默认是0。也就是每一次。
G1垃圾回收器
- 统一收集新生代和老年代
- 把Java堆内存拆分为多个大小相等的Region
- 有逻辑上的新生代和老年代的:新生代包含某些Region,老年代包含某些Reigon
- 可设置垃圾回收的预期停顿时间,例如可以保证1小时内,STW,不超过1分钟,-XX:MaxGCPauseMills 配置,默认200ms,配置少了,会频繁gc,配置多了一次gc停顿时间太长。
- Region随时会属于新生代也会属于老年代,不存在新生代和老年代分配多少内存,新生代和老年代的内存区域不停变动,由G1控制
- -XX:+UseG1GC 启用
- 默认新生代占堆总内存5%,可以通过-XX:G1NewSizePercent 配置。运行中会自动调整增加,但占比上线不会超过默认60%,可以通过-XX:G1MaxNewSizePercent配置。
- 新生代+老年代混合回收,-XX:InitiatingHeapOccupancyPercent,默认45%,当老年代占45%个region的时候,会尝试触发混合回收。
G1回收原理:
G1通过把内存拆分为大量小Region,追踪每个Region中可回收对象大小及预估时间,尽量控制在指定的时间范围内,同时尽量回收更多的垃圾对象。
每个Region的大小 等于 堆大小/2048。JVM最多有2048个Region。Region的大小必须是2的倍数。
手动指定Region大小:-XX:G1HeapRegionSize
新生代依然有 eden survivor 概念,跟其他的一样。
一旦新生代达到了设定的占据堆内存的最大大小60%,且Eden区占满了对象(或者估算达到回收停顿时间)。会触发新生代的GC,G1会用复制算法进行垃圾回收,STW。回收时会追踪每个region回收所需时间,选择一部分region回收来保证gc停顿时间控制在指定范围内。
进入老年代算法也一样,年龄及动态年龄判断。
大对象Region 大对象判定规则:一个大对象超过了一个Region大小的50% 一个大对象如果太大,可能会横跨多个Region
新生代老年代回收的时候,会对大对象Region一起回收。
会根据配置的gc停顿时间给新生代不停分配更多Region。直到估算本次回收差不多是gc停顿时间左右,就进行一次新生代的gc。
G1垃圾回收过程
1.初始标记 STW,仅仅标记GC Roots直接引用对象,速度快。
2.并发标记 系统程序运行的同时进行GC Roots追踪,从GC Roots开始追踪所有的存活对象,比较耗时
3.最终标记阶段 STW,找到并发标记阶段修改过的对象,标记哪些还存活,哪些是垃圾。
4.混合回收阶段
计算老年代中每个Region中
- 存活对象数量
- 存活对象的占比
- 执行垃圾回收的预期性能和效率。
然后STW,让垃圾回收的停项时间控制在我们指定的范围内,选择Region回收。
最后这阶段可以执行多次,-XX:G1MixedGCCountTarget 参数控制,最后一个阶段执行几次混合回收。
这样可以让系统停顿时间不要过长。
G1整体都是基于 复制算法 进行回收。
-XX:G1HeapWastePercent 默认 5%, 意思是本次回收空闲出的region达到堆内存总的5%,就会停止混合回收,本次GC结束。
-XX:G1MixedGCLiveThresholdPercent,默认85%,意思Region中存活对象低于85%,才可被回收。
如果拷贝中发现没有空闲region可以承载了,则会切换到单线程标记,清理,压缩。非常慢。
G1优化
思路也是避免大量对象进入老年代。 主要还是通过控制-XX:MaxGCPauseMills 配置进行优化。
回收算法
新生代:复制算法
新生代分为三块:1个Eden区,2个Survivor区 其中Eden区,占80%,每一个Survivor区域占10%
可以使用的就是 eden 区 和 其中一个 survivor 区。
对象分配在eden区,如果eden区快满了,此时触发垃圾回收,将eden区中存活的对象 和 现在 survivor 区中上次存活的对象, 放到另一块 survivor 区。
优点是,只有10%的内存是闲置。
老年代: 标记整理算法
gc日志
使用 GCEasy 网站分析GC情况。
常用JVM相关工具
jstat
jstat -gc PID 1000 10
每隔一秒更新出最新的一次统计,共执行10次
列名 | 含义 |
---|---|
SOC | From Survivor区大小 |
S1C | To Survivor区大小 |
SOU | From Survivor区当前使用内存大小 |
S1U | To Survivor区当前使用内存大小 |
EC | Eden区的大小 |
EU | Eden区当前使用的内存大小 |
OC | 老年代的大小 |
OU | 老年代当前使用的内存大小 |
MC | 方法区(永久代、元数据区)的大小 |
MU | 方法区(永久代、元数据区)的当前使用的内存大小 |
YGC | 系统运行迄今为止的Young GC次数 |
YGCT | Young GC的耗时 |
FGC | 系统运行总Full GC次数 |
FGCT | Full GC的耗时 |
GCT | 所有GC的总耗时 |
该工具可以推算
- 每秒新增多少的对象。
- GC次数,平均耗时
- 根据YGC频率,以此时间,监控老年代增长。
jmap
jmap -histo PID 按照各种对象占用内存空间大小降序排列。可以快速查到当前内存中哪个对象占用了大量内存。
jmap -dump:live,format=b,file=dumpname.hprof PID
生成堆转储文件,可以用MAT或者jhat分析,一般用于解决内存溢出。
jhat
jhat -port 8080 dumpname.hprof 用于分析堆转储文件
开启一个自定义端口的服务,可通过浏览器访问。
经典优化
新生代垃圾回收优化
-
Survivor空间是否足够
如果不够,则可能频繁全扔到老年代,可以适当调大。
-
新生代对象,升入进入老年代的年龄。
-XX:MaxTenuringThreshold,可以适当调小,减少在年轻代占用,可以根据young gc 频率,结合对象存活时间估算。
-
直接进入老年代对象大小
-XX:PretenureSizeThreshold=1M 一般给个 1M 就行,防止在年轻代复制来复制去,消耗性能
-
指定垃圾回收器
-XX:+UseParNewGC
老年代垃圾回收优化
根据触发Full GC的几个场景来看,都是老年代空间不足才触发,所以基本上只要是年轻代尽量都消灭,且老年代只保留长久不回收的例如单例对象,即可。所以在年轻代优化完备后,老年代只需保证空间充足,不过于小即可。
大内存应用
使用G1,控制每次回收时间,不等到集赞特别多才回收。
G1 停顿时间合理预估
过小GC频繁,过大停顿之间过长,预估及配合APM,jstat等监控,找到可接受范围内的找到中间值即可。