您的位置:首页 > 其它

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 failed
concurrent 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中不好实现动态更新的原因之一,而其他更多的原因是对象状态的复制、依赖的设置等等


工具

jstack
jstack 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配置信息


参考

追风堂公共课:《初探CMS
GC算法原理和实例分析》 JVM交流答疑圈-问答归档(20140228)
线上jvm参数分析
理解Hotspot JVM CMS垃圾回收器
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: