jvm
加载的类是放到了JVM的元数据空间,也就是永久代
永久代一般放一些类和常量池,一般设置256M. 如果给小了,可能导致频繁的Full GC,因为永久代如果满了,会触发Full GC.
Java虚拟机栈 1M的大小,一个系统运行最多几百个线程,不用设置太大.
局部变量保存的都是对象的地址,地址指向了JVM堆内存。
1.类加载机制
加载-验证-准备-解析-初始化-使用-卸载
-验证规范-分配内存空间(静态变量)-符号应用替换为直接引用-赋值(new时触发加载初始化,有父类先初始化父类)
类加载器分类:
启动类加载器Bootstrap ClassLoader:lib目录下核心类库
扩展类加载器Extension ClassLoader:lib\ext目录下类
应用程序类加载器Application ClassLoader:java代码
自定义类加载器:依需定制
双亲委派机制:按加载器顺序,先找父亲去加载不行再找儿子;避免类重复加载
1.1. 防反编译?
类加密,使用自定义类加载器解密加载。 或者付费购买。
2.内存区域
方法区(1.8之后叫元数据空间): .class加载进来的类 和 一些常亮
程序计数器: 记录当前执行的字节码指令的位置,每个线程一个
java虚拟机栈: 方法的局部变量,每个线程一个
java堆内存: 代码中创建的对象。
流程图
2.2.JVM堆内存分代模型:大数对象短时间存活,少数对象长期存活
年轻代:创建对象
老年代:在新生代年龄较大的对象会进入老年代(15次youngGc) -XX:MaxTenuringThreshlod = 15
动态对象年龄规则:Survivor区内年龄1+年龄2+年龄n的多个年龄对象总和超过Surivior区的50%,此时年龄n以上的对象都放老年代
大对象直接进老年代: -XXPretenureSizeThreshold=1048576
MinorGC后存活对象太多无法放入Survivor区直接进老年代。
永久代: 其实就是方法区 存在类信息
方法区会不会垃圾回收? 满足三个条件会回收
该类的所有实例对象在堆内存中被回收;
加载这个类的classLoader已经被回收;
该类的class对象没有任何引用。
3.垃圾回收
Minor Gc/Young GC:
触发:创建新对象时新生代内存空间不足
哪些对象可以回收: 可达性分析算法;每一个对象分析有谁在引用,一层一层往上找,看是否有一个GCRoot
GCRoot: 方法的局部变量,类的静态变量是; 类的实例变量不是
有GCRoot引用的对象不能被回收;如果是软引用或弱引用也可能被回收。
复制算法:把新生代划分为两块内存区域,只使用其中一块内存,快满时把里面的存活对象一次性转移到另一块区域,保证没有内存碎片。
复制算法缺点:只有一半的内存在使用,内存使用效率低。
复制算法优化:(一次新生代垃圾回收过后99%的对象其实都被回收,只有1%的存活)1个Eden区+2个Survivor区;Eden区占80%空间,Survivor各占10%内存空间;Eden区快满触发垃圾回收->转移存活对象到空Survivor->清空Eden->新对象进Eden->再次触发GC时转移Eden和Survivor区的存活对象到另一空Survivor区。
-XX:SurvivorRatio=8 默认Eden比例80%;
Full GC:
触发:老年代空间担保规则:在执行任何一次MinorGC之前都会判断老年代可用空间是否大于所有新生代对象之和,若不大于并且“-XX:-HandlePromotionFailure”(jdk1.6后废弃,默认开启)参数没设置会直接触发FullGC;
如果设置了参数,MinorGC后存活对象大小大于Survivor区也大于老年代会触发FullGC.
FullGC后,老年代还是没有足够空间存放MinorGC过后的存活对象->OOM内存溢出;System.gc()
标记整理算法: 通过追踪GCRoot标记垃圾对象,清理垃圾对象
垃圾回收线程和系统工作线程尽量同时进行:
1.初始标记: Stop the word; 标记GCRoot直接引用的对象。
2.并发标记: 与系统程序并发运行; 对老年代所有对象进行GCRoot(包括间接引用)追踪,耗时长,不影响系统。(会占用CPU资源)
3.重新标记: Stop the word; 重新标记第二阶段新创建的对象以及可能失去引用变成垃圾的对象。
4.并发清理: 并发清理标记为垃圾的对象;耗时,不影响系统。
整理内存碎片
3.1.1. Stop the Word:
GC期间不允许java进程在创建新对象,让垃圾回收期专心工作,JVM进入Stop the word状态。会造成系统停顿。
3.1.2 垃圾回收器
ParNew: 用于新生代,多线程并发,性能较好。
回收算法: 复制算法
指定使用: -XX:+UseParNewGC
默认线程数: 同cpu核数一样; -XX:ParallelGCThreads也可自己设置
CMS: 用于老年代,多线程并发,性能较好。 与ParNew一般是线上生产系统的标配组合。
回收算法: 标记清理算法
默认线程数: (CPU核数+3)/4
-XX:CMSInitiatingOccupancyFaction: 设置年老代占用多少比例的时候触发CMS垃圾回收,默认92%;预留一定空间给并发回收期间,系统把一些新对象放入老年代。
Concurrent Mode Failure: CMS垃圾回收期间,系统程序要放入年老代的对象大于可用内存空间,垃圾回收失败;会自动用Serial Old垃圾回收期替代CMS,强行Stop the word.
-XX:+UseCMSCompactAtFullCollection: 默认打开,FullGC后再次进行Stop the word,进行碎片整理。
-XX:CMSFullFCsBeforeCompaction: 默认0,执行多少次FullGC再进行碎片整理。
G1: 统一收集新生代 和 老年代,采用更加优秀的算法和设计。
把堆内存拆分成多个大小相等的Region,Region可能属于年轻代也可能属于年老代。
指定使用: -XX:+UseG1GC
JVM最大Region数: 2048, -XX:G1HeapRegionSize:手动设置region大小;region大小必须是2的倍数。
-XX:G1NewSizePercent: 新生代对堆内存初始值占比,默认5%。
-XX:G1MaxNewSizePercent: 新生代最大占比,默认60%。
仍有Eden和Survivor划分,各自占据不同的Region.
-XX:MaxGCPauseMills: 设定目标GC停顿时间(重点优化),默认200ms.
YoungGC触发:新生代达到设置堆内存的最大比例,并且Eden区满了。 复制算法同ParNew,不同在于会对每个Region追踪回收他需要多少时间,选择回收一部分Region,保证停顿时间可控。
对象进老年代规则: 同ParNew+CMS
大对象Region: 一个对象超过了一个Region大小的50%,会放入大对象专门的Region.(同新生代老年代一起回收)
-XX:InitiatingHeapOccupancyPercent: 默认45%,老年代占用堆内存45%时触发新生代+老年代混合回收。
回收过程:
初始标记: Stop the word,标记GCRoot直接引用的对象
并发标记: 并发,追踪所有存活对象.并记录对对象修改做记录。
最终标记: Stop the word,对并并发标记做的记录最终标记。
混合回收: 计算老年代每个Region中的存活对象数量,占比,执行垃圾回收的预期性能和效率。接着Stop the word,全力回收。
-XX:G1MixedGCCountTarget: 默认8,最后一阶段执行几次混合回收。
-XX:G1HeapWaastePercent: 默认5%,Region回收都是基于复制算法。
-XX:G1MixedGCLiveThresholdPercent: 默认85%,region存活对象低于85%的才可以回收。
回收失败,单线程FullGC,标记清理,压缩整理。
G1适合超大内存机器,因为内存大不用G1会导致新生代每次GC回收垃圾多停顿时间长,G1可以设置停顿时间。
Serial: 新生代; 单线程运行,垃圾回收时系统直接卡死,基本不用。
Serial Old: 老年代; 单线程运行,垃圾回收时系统直接卡死,基本不用。
垃圾回收器单线程好还是多线程好?
java程序 -server启动服务器模式,多线程可充分利用cpu
java程序 -client启动客户端模式,单线程好,多线程会导致单CPU运行多个线程频繁上下文切换。
3.1.3. FullGC为什么比YoungGc慢?
1.存活对象多,GCRoot追踪链路长
2.寻找零零散散的垃圾对象
3.碎片整理,stop the word
4.线上设置jvm内存大小
-Xms: java堆内存大小
-Xmx: java堆内存最大大小
-Xmn: java堆内存中新生代大小,剩下的就是老年代
-XX:MetaspaceSize: 永久代大小
-XX:MaxMetaspaceSize: 永久代最大大小
-Xss: 每个线程的栈内存大小
java -Xms512M -Xmx512M -Xmn256M -Xss1M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -jar app.jar
4.1.打印JVM GC日志
-XX:+PrintGCDetils 打印详细GC日志
-XX:+PrintGCTimeStamps 打印每次GC发生的时间
-Xloggc:gc.log 可以设置将GC日志写入一个磁盘文件
5.1. jstat查看线上JVM运行情况
jstat -gc PID:
S0C: From Survivor区的大小
S1C: To Survivor区的大小
S0U: From Survivor区当前使用的内存大小
S1U: To Survivor区当前使用的内存大小
EC: Eden区大小
EU: Eden区当前使用的内存大小
OC: 老年代大小
OU: 老年代使用大小
MC: 方法区(永久代、元数据区)大小
MU: 方法区(永久代、元数据区)使用大小
YGC: 系统运行迄今为止Young GC次数
YGCT: Young GC耗时
FGC: 系统运行迄今为止Full GC次数
FGCT: FullGC耗时
GCT: 所有GC总耗时
jstat -gc PID 1000 10: 每隔1秒更新最新一行jstat统计信息,执行10次
jstat -gccapacity PID: 堆内存肥西
jstat -gcnew PID: 年轻代GC分析
jstat -gcnewcapacity PID: 年轻代内存分析
jstat -gcold PID: 老年代GC分析
jstat -gcoldcapacity PID: 老年代内存分析
jstat -gcmetacapacity PID: 元数据内存分析
5.2. jmap/jhat 线上系统的对象分布
jmap -heap PID : 打印堆内存相关信息,没jstat全
jmap -histo PID:按照对象占用内存大小降序排列。 (只能简单看出对象占用内存情况)
num #instances #bytes class name
1 44660 1111232 java.lang.String
jmap -dump:live,format=b,file=dump.hprof PID 生成堆内存快照(二进制文件)
jhat dump.hprof -port 7000: 启动jhat服务器,web浏览器图形化方式分析堆内存快照
6.频繁FullGC, OOM
Metaspace溢出: 动态生成大量类且不能被回收 (大量反射)
JVM栈内存溢出: 无限制调用方法,每次调用都会有栈桢进栈。 (死循环bug)
JVM堆内存溢出: fullGC后老年代还不够用 (高并发,大对象/ mongo查询写错)
系统OOM的时候自动dump内存快照:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/local/app/oom
8G内存机器JVM配置模板:
-Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0
“-XX:TraceClassLoading -XX:TraceClassUnloading” 追踪类加载和类卸载的情况。