类加载顺序
- 加载: 使用类加载器将class文件加载到内存中;
- 验证: 验证class文件是否符合jvm的规范;
- 准备: 为class类和里面的static变量准备内存空间, 并未一些类型赋予初始值, 比如int类型赋予0;
- 解析: 将符号引用变为直接引用(待补充);
- 初始化: 实例化一个对象, 这里是真正的为static变量以及static块中的数据赋值, 此时, 如果该类的父类不没有被加载, 会先加载父类, 初始化的时间, 则是该类被new的时候, 就是这个类被使用的时候;
- 使用: 就是使用类执行代码逻辑;
- 卸载: 卸载这个类;
类加载的双亲委派机制
- 前提: 一个类被不同的类加载器加载, 会则会变为两个不同类, 因此一个类只能有一个唯一的类加载器加载;
实现上述原则, 使用双亲委派模型:
#启动类加载器: Bootstrap ClassLoader, 加载jdk lib目录下在类; #扩展类加载器 Extention classCloader, lib/ext目录下的类; #应用程序类加载器 Application classLoader, classPath下的类; #自定义类加载器 根据自己的需求去加载某些类; 执行机制: 每个classLoader拿到类之后, 都不会自己去加载, 而是交给 父加载器加载, 一层一层向上传递, 如果最后父类都不能加载, 则在向下 传递, 如果都找不到, 就会抛出异常;
jvm的分区
元数据区
用来存放类相关的数据, 比如我们加载的class文件;
堆内存
用来我们在执行程序的过程中创建的各种实例对象, 这是jvm优化最重要的部分;
java虚拟机栈
每一段代码的执行都是由一个线程执行的, 包括main函数, 也是由main线 程, 因此每一线程都有自己的虚拟机栈, 来保存自己的局部变量, 方法也 有自己的虚拟机栈, 当类执行完成, 或者方法执行完成, 这些局部变量就 会从栈中弹出, 注意, 这里的局部变量只是一个引用, 而真正的实例是在 堆内存中;
程序计数器
由于系统在执行程序的时候并不是一直在执行这个系统的功能, 而是cpu 在不同的进程和线程中来回切换, 因此要有一个区域来记录这个代码执行 到什么位置, 保证下次cpu来执行这段代码的时候直到从哪里开始, 程序 计数器就是这个作用;
其他数据区
例如和NIO相关的数据区, socket网络相关的数据区;
JVM的分代模型
年轻代
在jvm中绝大多数的类存在时间是很短的, 用完之后就直接卸载掉, 还有一部分类是要长期使用的, 比如我们的启动类, 以及启动类中的静态变量, 因此存在时间短的会保存在年轻代;
老年代
这里就是用来保存长期存活的对象的;
永久代
永久代就是上面提到的元数据区(jdk1.8以前叫方法区);
对象在各个区域时如何流转的
我们创建的对象, 会先进入年轻代, 随着程序的不断运行, 创建的对象越来越多, 年轻的空间会被逐渐占满, 此时就会触发一次Yong GC, 来回收此时已经不被使用的对象, 当这个对象躲过15次Yong GC还没被回收的时候, 这个对象就会被放进老年代;
JVM参数解释
-Xms: java堆内存大小; -Xmx: java堆内存最大大小; -Xmn: java堆内存新生代大小; -XX:MatespaceSize: 永久代大小; -XX:MatespaceSize: 永久代最大大小; -Xss: 每个线程栈内存大小; XX:MaxTenuringThreshold: 对象经历多次Yong GC之后进入老年代; -XX:PretenureSizeThreshold: 多大的对象直接进入老年代(默认字节); -XX:-HandlePromotionFailure: 这个参数用来在每次Yong GC之前检查老年代是否能存下年轻代现在所有的对象, 如果配置了这个参数, 即使存不下, 也不会触发老年代gc, 如果没有配置则会触发老年代gc(jdk1.6之后废弃); -XX:SurvivorRatio=8: 设置survivor区域进行动态年龄判断剩余空间百分比; -XX:UseParNewGC: 指定新生代使用ParNewGC垃圾回收器; -XX:ParallelGCThreads: 设置ParNew gc时的线程数, 一般不建议设置; -XX:CMSInitialtingOccupancyFaction: 定义老年代占用内存到了多少后就会自动触发垃圾回收, jdk默认是92%; -XX:UseConcMarkSweepGC: 老年代使用cms垃圾回收器; -XX:CMSFullGCsBeforeCompaction=0: 设置每次full gc之后进行内存碎片整理; -XX:+PrintGCDetils: 打印详细的gc日志; -XX:+PrintGCTimeStamps: 打印出来每次GC发生的时间; -Xloggc:gc.log: 将gc写入一个磁盘文件; -XX:CMSParallelInitialMarkEnabled: cms初识标记阶段开启多线程; -XX:CMSScavengeBeforeRemark: cms开始重新标记之前, 先执行一次yong gc; -XX:TraceClassLoading -XX:TraceClassUnloading: 可以用来跟踪类的加载和卸载; -XX:+DisableExplicitGC: 禁止使用代码调用gc
可以用如下方式设置:
如何根据自己的系统配置jvm
- 需要记录web应用的tps, 以及yong GC时间间隔和空间差值;
- 这样就可以知道, 每秒钟的请求量, 以及每次GC时间间隔和空间, 使用公式: 每个请求平均占用空间=空间/(tps*GC);
- 每个网站都有访问高峰(根据业务而定)或者公司规定的tps要达到的要求, 使用峰值tps*每个请求的空间=新生代空间大小
- 调优的目的: 要是jvm不进行老年代垃圾回收, 同时新生代回收频率较低, 则要让所有的对象都在新生代保存, 这样能保证系统的稳定性;
- 一般来说新生代和老年的空间大小是分配的相同的, 这样就可以估算出java堆内存的最小大小, 然后就可以根据自己的系统运行情况分配内存;
对于永久代和栈内存, 没有可以参考的规范, 一般前者设置几百兆的空间, 后者设置1M左右的空间, 应该是足够的, 这些区域在有些情况下也会发生溢出现象, 根据实际情况调整;
以上的只是估算, 而且只是一个大概的估算的方法, jvm调优是一个反复调试的过程, 而且每个系统在不同情况下的内存需求也不相同;
通常都使用4核8G内存服务器, 可以应对大部分web应用;
##### 对象的引用
- jvm使用可达性分析算法查找哪些对象还在被引用, 一般方法(方法未
从栈区弹出)的局部变量和类的静态变量引用的对象都是不会被回收的; 对象的引用分为强引用, 软引用, 弱引用和虚引用
强引用: 根据1中来判断;
软引用: 一般不会回收, 但是当内存空间不足时, 会将软引用回收;
弱引用: 每次垃圾回收都会回收引用对象;##### 垃圾回收算法 > 复制算法(新生代)
基本原理:
将内存区域分为两部分, 每次使用的时候只向一个内存区域保存数
据, 当一个内存区域要满的时候, 进行引用查询, 将还存活的对象保存到
另一个区域中, 然后将原来的内存一次清空;
缺点:
内存的使用率太低, 因为每次垃圾回收的对象只是一小部分, 却需要
一大块内存来保存;
解决办法: 引入Eden区域和两块survivor区域
survivor区域占内存的10%, Eden区域占新生代内存的80%, 每次存
活的对象保存进其中一个survivor区域中;> 标记清理算法(老年代)
就是标记处哪些对象存活, 哪些对象已经不被引用, 将不被引用的对象清
理掉, 存活的对象移动到内存区域的集中位置, 避免内存碎片;##### 对象什么时候进入老年代
- 15次yang gc之后, 依然没有回收的对象进入老年代(可配置);
- 动态年龄判断: survivor区域存放对象超过区域的50%, 那么此时大于等于survivor区域内对象年龄的对象会被放到老年代;
- 大对象直接进入老年代;
- Yong GC之后, 存活对象survivor区域不能保存;
老年代Full GC的触发条件
- YongGC之前的检查, 如果每次yong gc之后存活对象的平均值大于老年代剩余空间, 触发;
老年代空间不足以存放对象时触发;
垃圾回收器分类
ParNew:
用于新生代, serial是原始的新生代垃圾回收器, parNew比他好的地方是可以使用多核, 启动多个线程, 而serial只能使用单线程, 基于复制算法;
CMS:
用于老年代, 基于标记清理算法, 原理:
基本: 垃圾回收线程和系统工作线程同时进行;
1. 初识标记: 停止系统运行, 对老年代对象标记, 查看哪些已经不被引
用(只判断GC Root直接引用的对象), 虽然stop world, 但是运行快;
2. 并发标记: 系统继续运行, 对已经标记的对象进行深入跟踪(查看GC
root引用还有哪些), 判断到底有没有被真正的引用, 比较耗时, 但系统
继续运行;
3. 重新标记: 停止系统运行, 对已经标记的和新加入的对象进行重新标
记, 速度快;
4. 并发清除: 系统运行, 对对象进行清除, 速度慢;
如果在上述阶段, 老年代的剩余内存不够用了, 就会触发Concurrent
Mode Failure, 自动使用old serial垃圾收集器进行垃圾回收, 此时就
会造成系统长时间停顿;
G1: 大数据系统使用的垃圾回收器;
不同于ParNew+CMS, G1自己就可以搞定年轻代和老年代, 他将堆内存区
域划分为多个Region区域, 老年代和年轻代只是逻辑上的区分, 他最好的
地方是可以设置多久的时间内gc时间不能超多多少时间, 主要是通过标记
region区域中的垃圾对象, 判断回收这个region区域需要多久来做到的;
region到底是属于新生代还是老年代, 其实在G1中是动态分配的, 这次是
新生代, 下次可能就是老年代了;
G1专有参数:
-XX:UseG1GC: 使用g1垃圾回收器, region区域默认有2048个, 分配大小使用堆内存大小/2048;
-XX:G1HeapRegionSize: 定义region大小, 大小只能是2的倍数, 1M, 2M, 4...;
-XX:G1NewSizePercent: 新生代初始时占用多少内存;
-XX:G1MaxNewSizePercent: 定义新生代最多占多少内存, 默认2%, 最大不超过60%;
-XX:MaxGCPauseMills: 设置停顿最大时间;
-XX:InitiatingHeapOccupancyPercent: 定义老年代占用多少内存时, 触发一次新生代和老年回收;
-XX:G1MixedGCCountTarget: 定义最后一个阶段几次混合回收;
-XX:G1HeapWastePercent: 回收时基于复制标记算法进行;
-XX:G1MixedGCLiveThresholdPercent: 定义存活对象低于多少时才去回收这个region;
G1特性:
新生代还是有eden和survivor区域的区分, 对于大对象也不同, 有专门
的存放区域, 既不属于新生代, 也不属于老年代, 每次垃圾回收的时候都
会去回收这个区域;
G1回收步骤:
1. 初识标记
2. 并发标记
3. 最终标记
4. 回收
为什么Yong gc比full gc速度快很多?
- 年轻代比老年代存活的对象要少很多;
老年代除了标记清除之外, 还要进行内存碎片的清理, 另外还有可能发生Concurrent Mode Failure;
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:这是Full GC的耗时
GCT:这是所有GC的总耗时
- jstat -gccapacity PID:堆内存分析
- jstat -gcnew PID:年轻代GC分析,这里的TT和MTT可以看到对象在年轻代存活的年龄和存活的最大年龄
- jstat -gcnewcapacity PID:年轻代内存分析
- jstat -gcold PID:老年代GC分析
- jstat -gcoldcapacity PID:老年代内存分析
- jstat -gcmetacapacity PID:元数据区内存分析
jstat -gc pid 1000 10: 每个一秒打印一次, 打印10次;
jmap -heap pid: 打印信息如下
Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 478150656 (456.0MB) NewSize = 10485760 (10.0MB) MaxNewSize = 159383552 (152.0MB) OldSize = 20971520 (20.0MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage: New Generation (Eden + 1 Survivor Space): capacity = 9502720 (9.0625MB) used = 883072 (0.8421630859375MB) free = 8619648 (8.2203369140625MB) 9.292834051724139% used Eden Space: capacity = 8454144 (8.0625MB) used = 840504 (0.8015670776367188MB) free = 7613640 (7.260932922363281MB) 9.941917242005815% used From Space: capacity = 1048576 (1.0MB) used = 42568 (0.04059600830078125MB) free = 1006008 (0.9594039916992188MB) 4.059600830078125% used To Space: capacity = 1048576 (1.0MB) used = 0 (0.0MB) free = 1048576 (1.0MB) 0.0% used tenured generation: capacity = 20971520 (20.0MB) used = 13647432 (13.015205383300781MB) free = 7324088 (6.984794616699219MB) 65.0760269165039% used
jmap -histo pid: 打印信息如下, 类占用空间大小
1543: 1 16 sun.reflect.GeneratedMethodAccessor33 1544: 1 16 sun.reflect.GeneratedMethodAccessor4 1545: 1 16 sun.reflect.GeneratedMethodAccessor5 1546: 1 16 sun.reflect.GeneratedMethodAccessor6 1547: 1 16 sun.reflect.GeneratedMethodAccessor7 1548: 1 16 sun.reflect.GeneratedMethodAccessor8 1549: 1 16 sun.reflect.GeneratedMethodAccessor9 1550: 1 16 sun.reflect.ReflectionFactory 1551: 1 16 sun.reflect.generics.tree.BottomSignature 1552: 1 16 sun.reflect.generics.tree.IntSignature 1553: 1 16 sun.reflect.generics.tree.VoidDescriptor 1554: 1 16 sun.security.util.AlgorithmDecomposer 1555: 1 16 sun.security.util.ByteArrayLexOrder 1556: 1 16 sun.security.util.ByteArrayTagOrder 1557: 1 16 sun.security.util.DisabledAlgorithmConstraints$Constraints 1558: 1 16 sun.util.calendar.Gregorian 1559: 1 16 sun.util.locale.provider.AuxLocaleProviderAdapter$NullProvider 1560: 1 16 sun.util.locale.provider.CalendarDataUtility$CalendarWeekParameterGetter 1561: 1 16 sun.util.locale.provider.SPILocaleProviderAdapter 1562: 1 16 sun.util.locale.provider.TimeZoneNameUtility$TimeZoneNameGetter 1563: 1 16 sun.util.resources.LocaleData 1564: 1 16 sun.util.resources.LocaleData$LocaleDataResourceBundleControl 1565: 1 16 websocket.drawboard.DrawboardContextListener
jmap -dump:live,format=b,file=dump.hprof pid
在当前目录下生成存储快照
jhat -J-mx768m -port 7000 dump.hprof
使用浏览器查看存储快照, 访问ip:7000
发生OOM的区域
- Matespace区域: 区域设置过小, 或者代码编写有问题, 加载了大量class
- 虚拟机栈: 无限制的调用方法, 比如递归调用, 循环调用;
- 堆内存: 即使在gc之后, 堆内存依然不够用;
#在发生oom时进行dump快照输出
-XX:+HeapDumpOnOutOfMemeryError
-XX:HeapDumpPath=/...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。