参考链接:http://www.cnblogs.com/leefreeman/category/1058724.html
JVM基本结构
可能通过上面的描述,大家对JVM运行流程有了一个粗略的认识,那么JVM内部到底是怎么执行一个class文件的呢,也就是上图中最后一步第6步的内部细节是怎样的呢?要了解这个问题,我们首先得看一下JVM的内部结构:
从这个结构不难看出,class文件被jvm装载以后,经过jvm的内存空间调配,最终是由执行引擎完成class文件的执行。当然这个过程还有其他角色模块的协助,这些模块协同配合才能让一个java程序成功的运行,下面就详细介绍这些模板,它们也是后面学习jvm最重要的部分。
内存空间:
JVM内存空间包含:方法区、java堆、java栈、本地方法栈。
方法区是各个线程共享的区域,存放类信息、常量、静态变量。
java堆也是线程共享的区域,我们的类的实例就放在这个区域,可以想象你的一个系统会产生很多实例,因此java堆的空间也是最大的。如果java堆空间不足了,程序会抛出OutOfMemoryError异常。
Java栈是每一个线程的私有的区间,它的生命周期与线程是相同的,一个线程对应一个Java栈,每次执行一个方法就会往栈中压入一个元素,这个元素叫“栈桢”,“栈桢”包括局部变量、用于存放中间状态值的操作栈。如果Java栈空间不足来,程序会抛出StackOverflowError异常,一般在线程比较多杭州递归调用方法很分深的情况,会占用大量的栈空间,会产生栈溢出的情况。
本地方法栈和Java栈类似,只是它用来表示执行本地方法,本地方法栈存放的方法调用本地方法接口,最终调用本地方法库,实现与操作系统、硬件交换的目的。
PC寄存器,说到这里,类,静态变量加载到方法区来,实例对象加载到Java堆来,方法加载到Java栈来,各自多去来各自该去的地方。又如何执行这些程序呢,其中PC寄存器中保存来程序执行的指令,它是控制程序指令的执行顺序。
执行引擎是执行PC寄存器分配该它的指令,指令的执行者。
垃圾回收算法
引用计数法
就是堆一个对象被引用的次数进行计算,当增加一个引用计算就加1,减少一个引用计算就减1.
如果计数器为0则表示该对象没有引用,则把该对象清除。
引用计算算法简单,但是Java没有用这种算法,因为这种算法不能删除循环引用,例如 A引用B ,B引用A这种情况,对象是不能被清除的。
标记清除
遍历所有的GC roots , 并从GC roots 科大的对象设置为可存活对象;然后变量堆中所有对象,把没有标记为存活的对象清除;
总结标记清除算法: 因为涉及到大量内存遍历工作,需要停在其他应用,需要的时间比较长,Java吞吐量比较低。
对象被清除自豪,被清除的对象留下内存空缺位置,造成内存不用连续,空间浪费了。
标记压缩算法:
标记压缩算法是在标记清除算法的基础上进行改进的。执行完标记清除操作之后,然后第一内存空间进行压缩,节省来内存空间,解决来标记清除算法内存不连续的问题。注意标记压缩算法也会产生“stop the world”,不能和java程序并发执行。在压缩过程中一些对象内存地址会发生改变,java程序只能等待压缩完成后才能继续。
复制算法:
负责算法是把内存空间一分为二,在垃圾回收的时候,遍历其中一块有对象的内存块,把可存活的对象迁移到另为一块内存区,然后把留有没有迁移的对象区域清空,完成垃圾回收。
复制算法相对压缩算法更简洁高效,但是也是有缺点也是显而易见,不适合用于存活对象多,因为那样需要复制的对象很多,复制性能较差,所以复制算法往往用于内存空间中新生代的垃圾回收,因为新生代中存活对象较少,复制成本较低。它另外一个缺点是内存空间占用成本高,因为它基于两份内存空间做对象复制,在非垃圾回收的周期内只用到了一份内存空间,内存利用率较低。
垃圾回收器可以看做一系列算法的不同组合,在不同的场景使用合适的垃圾回收器,才能起到事半功倍的效果。
垃圾回收器
Java堆的结构:
Java堆内存结构包括:新生代,老年代,其中新生代由一个伊甸区和2个幸存去组成,2个幸存区是大小相同,完全对称,没有任何差别的。我们把他们称为from区和to区。
JVM的垃圾回收主要是针对以上堆空间的垃圾回收。
下面是垃圾回收器。
串行收集器:是gc单个线程对进行垃圾回收,其中新生代使用复制算法,老年代使用标记压缩算法。
gc单线程进行垃圾回收时,其他应用线程需要暂停,只用垃圾回收完之后,其他应用线程在继续运行。
串行垃圾回收器,回收时间比较长,但是相比其他回收器,其稳定性比较好。
并行垃圾回收器:
并行回收器是在串行回收器基础中改进,是用gc多线程并发进行垃圾回收。并行回收器有两种
1、 ParNew回收器
这个回收器只针对新生代进行并发回收,老年代依然使用串行回收。回收算法依然和串行回收一样,新生代使用复制算法,老年代使用标记压缩算法。在多核条件下,它的性能显然优于串行回收器,如果要使用这种回收器,可以在启动参数中配置:
-XX:+UseParNewGC
如果要进一步指定并发的线程数,可以配置一下参数:
-XX:ParallelGCThreads
ParNew回收器的流程如下图所示:
在进行垃圾回收时应用程序线程依然被暂停,GC线程并行开始执行垃圾回收,垃圾回收完成后,应用程序线程继续执行。
2、 Parallel回收器
依然是并行回收器,但这种回收器有两种配置,一种类似于ParNEW:新生代使用并行回收、老年代使用串行回收。它与ParNew的不同在于它在设计目标上更重视吞吐量,可以认为在相同的条件下它比ParNew更优。要使用这种回收器可以在启动程序中配置:
-XX:+UseParallelGC
Parallel回收器另外一种配置则不同于ParNew,对于新生代和老年代均适应并行回收,要使用这种回收器可以在启动程序中配置:
XX:+UseParallelOldGC
Parallel回收器的流程和ParNew的流程是一致的:
在进行回收时,应用程序暂停,GC使用多线程并发回收,回收完成后应用程序线程继续运行。
多线程回收比单线程回收效率高。
cms回收器:
cms回收器是一个只能针对老年代的回收器。它是采用标记清除算法。其他应用线程不用暂停,cms回收线程可以和其他应用线程并行运行,其他回收线程遍历内存区中对象,对有应用可到达的对象标记为存活,多次对对象进行标记,把那些对象标记为不存活的对象清除掉。
cms回收器有一个有不足的地方就是 每次的清理的不彻底,所以会频繁的进行GC线程操作,影响来其他应用线程的吞吐量。
G1回收器:
G1回收器不同与其他回收器,它是将堆空间划分为多个独立的区块,每一个区块既有属于老年代的,也有属于新生代。并且每类区域空间可以是不连续的。每一块区域中需要回收的对象多少不同,G1会优先把多的垃圾区进行回收,这用可以用少量的时间回收更多的垃圾对象。
G1相对cms回收器优点:
1.因为划分很多区块,回收是减少来内存碎片的产生;
2.G1适用新生代和老年代,而cms只适用老年代。
参考链接:http://www.cnblogs.com/leefreeman/p/7509278.html
对jvm故障排查流程:
案例:
1.通过jps命令查看服务是否正常运行。
2.通过top命令查看进程使用cpu 内存以及负载情况。
3.如果以上多正常,说明是堆内存是正常的,所以坚持线程栈。
4.使用jstack命令导出线程栈信息。
下面介绍jdk自带的使用工具:jsp jstat jmap jstack
jsp 列出目前服务器上运行的Java程序及进程id;
jstat 用于输出Java程序内存使用情况,包括新生代,老年代,元数据容量、垃圾回收情况等。
jmap:用于输出java程序中内存对象的情况,包括有哪些对象,对象的数量。
jmap -histo 3618
上述命令打印出进程ID为3618的内存情况。但我们常用的方式是将指定进程的内存heap输出到外部文件,再由专门的heap分析工具进行分析,例如mat(Memory Analysis Tool),所以我们常用的命令是:
jmap -dump:live,format=b,file=heap.hprof 3618
将heap.hprof传输出来到window电脑上使用mat工具分析:
jstack:用户输出java程序线程栈的情况,常用于定位因为某些线程问题造成的故障或性能问题。
jstack 3618 > jstack.out
上述命令将进程ID为3618的栈信息输出到外部文件,便于传输到windows电脑上进行分析。
内存溢出原因: 堆内存溢出、元空间、线程栈、直接内存。
堆内存OOM
使用内存超过来申请的堆内存,则想方法增加堆内存空间,在时间开发中有必要去掉引用关系。
元空间OOM
代码循环参加class ,大量class元数据,存放在元数据区超过设置的4M空间,在元空间溢出
栈OOM
当创建的线程是jvm会给每一个线程分配栈内存,当创建线程过多;占用的线程也就越多,这中情况会导致栈OOM
直接内存OOM
ByteBuffer的allocateDirect方法可以申请直接内存,当申请的内存超过的本地可用内存时,会报OOM: