本文主要参考内容:
http://hllvm.group.iteye.com/group/wiki/3053-JVM
http://my.oschina.net/xishuixixia/blog/133850
http://my.oschina.net/xishuixixia/blog/132395
http://www.cnblogs.com/gw811/archive/2012/10/18/2730117.html#undefined
1. JVM 内存模型:
程序计数器寄存器(The pc Register)
程序计数器寄存器一块较小的内存,它的作用可以看作是当前线程锁执行的字节码的行号指示器。任一时刻一个处理器只会执行一条线程中的指令,为了线程切换
后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器。如果方法不是native的,程序计数器寄存器包含了当前执行的JVM指令的地址,如果方法是 native
的,程序计数器寄存器的值不会被定义(Undefined)。
Java虚拟机栈
当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来存储局部变量表、操作栈、动态链接、
方法出口等信息。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)。栈
以帧为单位保存线程的状态。JVM对栈只进行两种操作:以帧为单位的压栈和出栈操作。栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线
程)为这个线程建立的存储区域,该区域具有先进后出的特性。
当嵌套方法调用时,嵌套越深,stack的内存就越晚才能释放,因此,在实际开发过程中,不推荐大家使用递归来进行方法的调用,递归很容易导致stack flow。
当线程请求的栈深入超过虚拟机所允许的深度,将抛出StackOverFlowError异常,若虚拟机动态扩展后仍无法请求到足够的内存,则抛出OutOfMemoryError异常。
本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是
为虚拟机使用到的Native方法服务。
堆
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例和数组都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”(Garbage Collected Heap,幸好国内没翻译成“垃圾堆”)。如果从内存回收的角度看,由于现在
收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然
Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
Java虚拟机规范对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集
行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般
来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。
常量池(运行时常量池?)
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool
Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。一般来说,除了保存Class文件中描述的符号引用外,还
会把翻译出来的直接引用也存储在运行时常量池中。 运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,
也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。既然运行时常量池是方法区的一部分,自然会受到方法区内
存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
对象的生成
最普通的程序行为,但即使是最简单的访问,也会却涉及Java栈、Java堆、方法区这三个最重要内存区域之间的关联关系,如下面的这句代码:
Object obj = new Object();
假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到Java栈的本地变量表中,作为一个reference类型数据出现。而“new Object()”这部分的语
义将会反映到Java堆中,形成一块存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现
的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实
现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
年轻代中的GC
HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。
因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
一个对象的这一辈子
我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
常用的调优参数。
1.堆大小
-Xms和-Xmx用于指定堆大小,我们需要将他们俩设置为一样的值,以避免在GC后重新调整堆的大小。
2.年轻代大小
-XX:NewSize=?和–XX:MaxNewSize=?,年轻代大小建议设置为堆大小的1/3或者1/4,两个值大小一样。设置年轻代大小相当重要,如果年轻代设置小了,那么一些可以生存周期短的对象可能被移到年老代,导致Full GC,而设置太大,也会引起stop the world。
3.方法区大小
-XX:PermSize=256m和-XX:MaxPermSize=256m,方法区大小不会影响性能,只要你设置的值保证应用不报OOM之类的错误即可。
4.GC日志
-Xloggc:$CATALINA_BASE/logs/gc.log、-XX:+PrintGCDetails、-XX:+PrintGCDateStamps,尽可能多的记录一些GC日志,这样可以全面的分析系统行为。
5.GC算法
-XX:+UseParNewGC、-XX:+CMSParallelRemarkEnabled、-XX:+UseConcMarkSweepGC、 -XX:CMSInitiatingOccupancyFraction=75,这只是一般系统中经常用的参数,你还需要根据你的系统情况选择最适合你的系统。
6.堆快照
-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath=$CATALINA_BASE/logs。
7.OOM发生之后的操作
OOM后关闭服务器:-XX:OnOutOfMemoryError=$CATALINA_HOME/bin/stop.sh
OOM后重启服务器: -XX:OnOutOfMemoryError=$CATALINA_HOME/bin/restart.sh