应用程序日志 JVM-完整结构
1.方法区
1>用于存放被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等
2.堆
1>新生代
Eden
Survivor(From)
Survivor(To)
2>老年代
3.程序计数器
1>当前线程执
一.JVM内存区域 1.方法区 1>用于存放被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等 2.堆 1>新生代 Eden Survivor(From) Survivor(To) 2>老年代 3.程序计数器 1>当前线程执行字节码的行号指示器 4.虚拟机栈 1>jvm执行java方法 java方法执行的内存模型,每个方法执行都会创建一个栈帧(局部变量表、操作数栈、动态链接、方法出口) 每个方法从开始调用到结束都对应一个栈帧在虚拟机中入栈到出栈的过程 5.本地方法栈 1>jvm执行本地方法 HotSpot把本地方法栈和虚拟机合二为一 6.直接内存 1>JDK1.4的NIO,可以使用native函数直接分配堆外内存,受本机总内存和处理器寻址空间的限制 二.JVM内存溢出 1.堆 1>OutOfMemoryError: java heap space 最大堆、最小堆、新生代:-Xms512M -Xmx512M -Xmn128M 2.虚拟机栈/本地方法栈 1>StackOverflowError: 每个线程可使用内存:-Xss256K 2>OutOfMemoryError: unable to create new native thread 3.方法区 1>OutOfMemoryError: PermGen space JVM设置最小最大:-XX:PermSize=64M -XX:MaxPermSize=128M 4.直接内存 1>at.sun.misc.Unsafe.allocateMemory(Native Method) 5.元空间 1>java1.8支持 2>java.lang.OutOfMemoryError: Metadata space -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M 三.类的加载 1.类的生命周期 1>加载(Loading) 通过一个类的全限定名来获取定义此类的二进制字节流 将二进制字节流代表的静态存储结构转化为方法区的运行时数据结构 代表该类的java.lang.Class对象,作为方法区这个类各种数据的访问入口 2>连接 验证(Verification) ->文件格式验证 ->元数据验证 ->字节码验证 ->符号引用验证 准备(Preparation) ->正式为类变量分配内存并设置初始值 解析(Resolution) ->把间接引用转换为直接引用 3>初始化(Initialization) 执行(),初始化类变量、静态代码块 4>使用(Using) 5>卸载(Unloading) 2.Java类加载器 1>启动类加载器(Bootstrap ClassLoader) 2>扩展类加载器(Extension ClassLoader) 3>应用程序类加载器(Application ClassLoader) 4>自定义类加载器(User ClassLoader) 四.垃圾回收 1.判断对象已死 1>引用计数算法 给对象添加一个引用计数器,每当一个地方引用它时计数器加1;引用失效计数器减一;计数器为0说明不再引用 优点:实现简单,判定效率高 缺点:无法解决对象相互循环引用的问题 2>可达性分析算法 当一个对象到GC Roots没有引用链相连(GC Roots到这个对象不可达时),证明此对象不可用 GC Roots种类 ->虚拟机栈(栈帧中的本地变量表)中引用的对象 ->方法区中的静态属性引用的对象 ->方法区中的常量引用的对象 ->本地方法栈中JNI(Native方法)引用的对象 3>引用 强引用:Object object = new Object(); 软引用:SoftReference object = new SoftReference(new Object()); 弱引用:WeakReference object = new WeakReference(new Object()); 虚引用:ReferenceQueue referenceQueue = new ReferenceQueue(); PhantomReference object = new PhantomReference(new String(), referenceQueue); 4>finalize() 2.垃圾收集算法 1>标记清除算法 缺点:标记和清除效率都不高,标记清除之后会产生大量不连续的内存碎片 2>复制算法(新生代) Eden:Survivor: Survivor=8: 1: 1 分配担保:如果Survivor没有足够空间来存放上次新生代GC存活下来的对象,他们将通过分配担保机制进入老年代 缺点:对象存活率高时,复制效率较低 3>标记整理算法(老年代) 先执行标记-清除,让所有的存活对象都向一端移动,最后清理掉边界以外的内存 3.垃圾收集器 1>新生代 Serial(连续) ->特点:单线程收集:标记-复制算法 ->场景:Client模式下默认新生代收集器:单核机器 ParNew(实现) ->特点:多线程并行收集:标记-复制算法;其他特点与Serial相似 ->缺点:在单CPU场景效果不突出 ->场景:用户交互,配合CMS垃圾收集器 Parallel Scavenge(并行清除) ->特点:目标在于达到可控吞吐量(吞吐量=用户代码运行时间/(用户代码运行时间+垃圾收集时间));标记-整理) ->场景:高效利用CPU,后台运算且不需要太多交互 2>老年代 CMS ->特点:最短回收停顿时间;标记-清除 ->步骤: *初始标记:标记GC Roots直接关联的对象,速度快 *并发标记:GC Roots Tracing过程,耗时长,与用户进行并发工作 *重新标记:修正并发标记期间用户进行继续运行而产生变化的标记,耗时比初始标记长但远小于并发标记 *并发清除:清除标记的对象 ->缺点: *对CPU资源敏感 *无法回收浮动垃圾 *标记-清除算法,会产生内存碎片,可以通过参数开启碎片的合并整理 Serial Old ->特点:Serial的老年代版本,单线程;标记-整理 ->场景:1.5之前与Parallel Scavenge配合使用;作为CMS的后备预案 Parallel Old ->特点:标记-整理;多线程 ->场景:为了替代Serial Old与Parallel Scavenge配合使用 3>G1 特点:将整个java堆划分为多个大小相等的独立区域Region,跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先 列表,每次根据允许的收集时间,优先回收价值最大的Region 步骤: ->初始标记:标记GC Roots直接关联的对象 ->并发标记:对堆中对象进行可达性分析,找出存活对象,耗时长应用程序日志,与用户进行并发工作 ->最终标记:修正并发标记期间用户进程继续运行而产生变化的标记 ->筛选回收:对各个Region的回收价值排序,然后根据期望的GC停顿时间制定回收计划 4>垃圾收集器搭配关系 4.垃圾回收过程 1>大多情况下对象在Eden分配,当Eden没有足够空间时将发生一次Minor GC 2>将Eden执行Minor GC后还不足以为对象分配空间,大对象直接进入老年代,可以用参数设置大对象直接进入老年代,避免频繁Minor GC 3>如果对象在Eden出生,发生MinorGC后仍然存活,且能被Survivor容纳,年龄加1,达到一定年龄进入老年代,默认15 4>占Survivor空间一半以上且年龄相等的对象,大于等于该年龄以上的对象直接进入老年代 5>发生Minor GC之前会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果大于说明MinorGC安全,否则会判断是否允许 担保失败,如果允许担保失败,判断老年代最大连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则尝试Minor GC,否则执行 FullGC 五.性能优化 1.常见问题 1>CPU Load过高导致系统不可用或tps急剧降低 2>YoungGC次数频繁 3>FullGC次数频繁 4>FullGC时间长 5>PermSpaceGC次数频繁 6>内存泄露、内存溢出 2.调优参考数据 1>系统运行日志 2>异常堆栈 3>GC日志 4>线程快照(threaddump/javacore文件) 5>堆转储快照(heapdump/hprof文件) 3.调优工具 1>jdk命令行工具 jps查看系统内所有HotSpot进程 jinfo显示虚拟机配置信息 jstat收集虚拟机运行数据 jmap生成指定进程堆转储快照(heapdump/hprof文件) jhat分析heapdump文件 jstack显示虚拟机线程快照 2>可视化工具 jconsole和jvisualvm查看内存回收情况 BTrace跟踪调试方法 jprofiler监控每个类的内存占用 MAT工具分析内存占用 4.优化方案 1>JVM配置 -Xms和-Xmx的值设置成相等 新生代尽量设置大一些 2>实现层面 避免创建过大的对象及数组 避免同时加载大量数据 集合中的对象用完后及时清空 在合适场景使用软引用、弱引用 尽量避免长时间等待外部资源(数据库、网络、设备资源等) 设置合理的线程数 (编辑:武汉站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |