JVM相关学习记录与总结(内存&GC&类加载&工具)
2016-11-18 18:16
387 查看
一、内存
分布
1 总分布图
2 堆内存分布
OOM
类别堆内存不足
本地内存不足
加载类的Perm区内存不足
原因
内存泄漏
空间设置太小
大量长期的大对象
内存泄漏
症状系统越来越慢,并伴随着CPU使用率过高。这主要是因为随着内存的泄漏,可用的内存越来越小,垃圾回收器频繁进行垃圾回收(FullGC一次接一次,每次耗时几秒甚至几十秒)。而垃圾回收是一个CPU密集型操作,频繁的GC会导致CPU持续居高不下,在有内存泄漏的场合,到了最后必然是伴随着CPU使用率几乎为100
系统运行一段时间,系统抛出OOM异常
虚拟机core dump
常见的原因
自定义类加载器。每个对象有一个它class的引用,相应也有它classloader的引用。反过来,类加载器也拥有它加载的所有class的引用,一旦类加载器内存泄漏,意味着一堆对象泄漏。
数组减长度,但内容不减。
Threadlocal变量不释放。
静态hash做缓存只增不减。
资源(连接池)不关闭。
大小的经验推荐
空间 | java参数 | 推荐设值 |
---|---|---|
堆内存 | -Xms & -Xmx | 稳定阶段,FullGC发生过后老年代空间占用大小的3-4倍 |
永久代 | -XX:PermSize & -XX:MaxPermSize | 稳定阶段,FullGC发生过后永久代空间占用大小的1.2-1.5倍 |
年轻代 | -Xmn | 稳定阶段,FullGC发生过后老年代空间占用大小的1-1.5倍 |
老年代 | 堆总内存-年轻代-永久代 | 稳定阶段,FullGC发生过后老年代空间占用大小的2-3倍 |
二、GC
CMS
标记方式:通过bitmap触发原因:
正常触发
设置UseCMSInitiatingOccupancyOnly+CMSInitiatingOccupancyFraction=80后只有当老年代空间占用达到80%时才触发CMS
设置CMSClassUnloadingEnabled后同时会清理perm区域
过程
初始标记:为了收集应用程序的对象引用需要暂停应用程序线程,只标记root对象(bitmap里面)【stop the world】。
并发标记:从第一阶段收集到的对象引用开始,遍历所有其他的活对象引用。
并发预清理:改变当运行第二阶段时,由应用程序线程产生的对象引用,以更新第二阶段的结果。
重标记:由于第三阶段是并发的,对象引用可能会发生进一步改变。因此,应用程序线程会再一次被暂停以更新这些变化,并且在进行实际的清理之前确保一个正确的对象引用视图。主要遍历young区和old/perm区的卡表(card table)【stop the world】。
并发清理:单线程处理,所有不再被应用的对象将从堆里清除掉,不压缩,所以会有碎片。
并发重置:收集器做一些收尾的工作,以便下一次GC周期能有一个干净的状态。
与fullGC的关系
FullGC指对老年代/永久代的stop the world的GC
FullGC的时间和次数分别指老年代GC时stop the world的时间和次数
CMS不等于FullGC,CMS分为多个阶段,只有stop the world的阶段被计算到了FullGC的次数和时间,而和业务线程并发的GC的次数和时间是不被认为为Full GC
ParNew(CMS处理年轻代的算法)
根据对象的引用关系,从root对象开始,只处理活的对象,采用move©的策略将活的对象都拷贝到to区域;old区对young区对象的引用是通过card table,遍历card table将可以引用到的活的对象也复制到to区域。Eden满时触发,多线程处理。
Mark-Sweep-Compact(CMS处理老年代的算法,进行碎片压缩)
过程
标记所有活的对象阶段。这个阶段根据root来地柜地标记所有活的对象,基本上等级于CMS的初始和并发标记的,但是是单线程的。计算活着的对象新的内存地址阶段。根据阶段1的结果,单线程遍历每个generation的每个内存区域的每个对象,计算活着的对象应该被compact到哪里,并把计算结果保存到header的mark上。
修改对象内部引用。顺序遍历每个generation每个内存区域每个对象,对于每个活着的对象,修改内部引用指向正确的内存地址。
移动对象。根据2把活着的对象copy到正确的内存位置。
触发
promotion failedconcurrent mode failure
System.gc()
GC的参数配置
1 堆配置相关
-Xms4g //最小堆-Xmx4g //最大堆
-Xmn2g //young区大小
-XX:SurvivorRatio=10 //eden与survivor区大小比例
2 perm配置相关
-XX:PermSize=256m-XX:MaxPermSize=256m
3 压缩指针
-XX:UseCompressedOops //64位下的内存设置,如果放到32位上(一般就4G),有时空间就超了,压缩指针可以一定程度帮助压缩来适应32位的大小
4 CMS配置相关
-XX:+UseConcMarkSweepGC-XX:+UseCMSCompactAtFullCollection //cms时进行碎片压缩
-XX:CMSMaxAbortablePrecleanTime=5000
-XX:+CMSClassUnloadingEnabled //CMS时也会清理perm区
-XX:CMSInitiatingOccupancyFraction=80 //指定老年代占比多少(这里是80%)时触发CMS
-XX:+UseCMSInitiatingOccupancyOnly //结合XX:CMSInitiatingOccupancyFraction一起使用
5 GC线程配置相关
-XX:ParallelGCThreads//设置并发阶段GC的线程的数量
6 GC LOG相关
-Xloggc:${LOGS}/gc.log-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
7 其他
-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=${LOGS}/java.hprof
三、类加载
过程
其中,加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的“开始”(仅仅指的是开始,而非执行或者结束,因为这些阶段通常都是互相交叉的混合进行,通常会在一个阶段执行的过程中调用或者激活另一个阶段),而解析阶段则不一定(它在某些情况下可以在初始化阶段之后再开始),这是为了支持Java语言的运行时绑定。
零碎知识点
显示与隐式:new是隐式加载,触发<init>和
<clinit>;Class.forName是显式,触发
<clinit>。
双亲委派:先交给父加载器加载,如果加载不了,就由自己来加载,如果所有都不能加载就抛出ClassNotFoundException异常。
类初始化
触发条件使用new关键字实例化对象的时候。读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
当初始化一个类时,发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
当虚拟机启动时,用户需要指定一个要执行的主类(包含main()的方法的那个类),虚拟机会先初始化这个主类。
内部过程
就是执行,编译器将所有类变量初始化语句和静态代码块都收集到中
常见错误
ClassNotFoundException从自定义类加载器到系统类加载器,到扩展类加载器,到引导类加载器,在不同路径下都找不到那个class
如果那个class的确存在,也有两个可能导致这个异常,一是jar冲突了,实际加载的那个class不是你想要的;二是不同的类加载器隔离了,当前类A引用了类B,类A的加载器加载了类A后,因为调用也去加载类B,但是类B指定只能是被ClassLoaderB来加载的,这是加载不到类B也会报这个异常。
NoClassDefFoundError
这个出现的场景一般是类A引用了类B,类A加载器加载类B时加载失败(不存在或者加载器隔离),这时会报ClassNotFoundException异常,并连带引发NoClassDefFoundError这个错误。也就是说在编译时这个类是能够被找到的,但是在执行时却没有找到。
【摘自java虚拟机规范】如果 Java 虚拟机曾经试图在 D 的验证或解析阶段、但又还没有进 行初始化时加载 C 类,当用于加载 C 的初始类加载器抛出 ClassNotFoundException 实例时,Java 虚拟机在D中必须抛出NoClassDefFoundError异常,它的 cause 字段中就保存了那个ClassNotFoundException异常实例。
LinkageError
此类已经在ClassLoader加载过了,重复的加载会造成这个异常
由于JVM的这个保护机制,使得在JVM中是没办法直接更新一个已经load了的Class的,只能是创建一个新的ClassLoader来加载更新了的Class,然后将新的请求转入到这个ClassLoader中来获取类,这也是JVM中不好实现动态更新的原因之一,而其他更多的原因是对象状态的复制、依赖的设置等等
工具
jstackjstack java_pid > jstack.out //输出堆栈信息
jmap
jmap -histo java_pid > mem.out//查看内存使用情况,某个类有多少实例,占用多少内存等
top
top -H -p java_pid //可以查看本地线程资源占用情况
printf("0x%x",nid)//将本地线程id转成16进制,方便在线程堆栈中搜索出对应的java线程
jps
jps -v //输出java_pid
jstat
jstat -gc/gcutil/gccapacity/gccause java_pid time_interval times//输出gc状态,可以指定输出次数和输出的时间间隔
jinfo
获取java配置信息
参考
追风堂公共课:《初探CMSGC算法原理和实例分析》 JVM交流答疑圈-问答归档(20140228)
线上jvm参数分析
理解Hotspot JVM CMS垃圾回收器
相关文章推荐
- Jvm 内存浅析 及 GC个人学习总结
- JVM内核学习 --内存相关,内存结构, GC,ClassLoader,内存溢出
- Entity Framework 学习总结之十:加载相关对象
- 回顾总结一下JVM(组成结构、GC、类加载)
- JVM系列二:GC策略&内存申请、对象衰老
- JVM系列二:GC策略&内存申请、对象衰老
- JVM内存结构以及GC原理学习
- JVM系列二:GC策略&内存申请、对象衰老
- JVM系列二:GC策略&内存申请、对象衰老
- JVM系列二:GC策略&内存申请、对象衰老
- jvm的GC的一些学习总结
- JVM系列二:GC策略&内存申请、对象衰老
- JVM系列二:GC策略&内存申请、对象衰老
- JVM虚拟机加载类到内存的ClassLoader.loadClass && Class.forName()的比较
- 20100811 学习记录:数据库相关的自动编号& update
- JVM学习之:类加载的过程总结
- JVM系列二:GC策略&内存申请、对象衰老
- JVM 性能监控与常用工具学习总结
- JVM系列二:GC策略&内存申请、对象衰老
- JVM垃圾回收(GC)整理总结学习