您的位置:首页 > 运维架构 > 网站架构

JVM调优系列(一)——JVM模型架构图解析

2016-06-03 18:02 288 查看
JVM模型架构图



、方法区

Method Area 方法区也是JVM内存模型中的重要内存区域,主要用于存放常量和类的定义信息。与Heap类似,也是被JVM中的线程共享。

常见异常:当方法区无法满足内存分配需求时,将抛出OutOfMemeryError异常

二、java 堆

Heap,java运行时内存中最重要的部分,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,分为新生代和老年代两部分,分别用于存放刚刚产生的对象和年轻的对象;如果对象一直没被回收,则被移入老年代。

1、新生代又进一步细分为 eden、survivor(s0,s1)

1)Eden

伊甸园,对象的出生地,大部分刚刚创建的对象会被放在Eden区。

2)Survivor

幸存区,存放那些经历过至少一次回收幸存下来的对象。如果幸存区中的对象到了指定年龄仍未被回收,则有机会进入老年代 Tenured。

注:堆是线程共享的

2、常见异常

如果堆中没有内存完成实例分配,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常

三、java 虚拟机栈
JVM language Stacks,也是线程的私有内存空间,和java线程在同一时间创建,主要保存方法的局部变量、部分结果等数据。

1、栈帧

每个方法在执行的同时都会创建一个栈帧(用于该方法的局部变量表、部分结果等信息的数据结构;方法的参数,局部变量越多,那么栈帧的局部变量表就会越大,栈帧会膨胀扩大内存以满足方法调用所需的参数传递,单个方法调用所需的栈空间也会随之增大。)栈帧的基本结构如下,每个区域都有各自的职责,用于存储方法的特定数据。

因为编码中最长涉及的就是局部变量表空间使用,所以这里着重分析栈中的局部变量表应用。

1.1、局部变量表

局部变量表(Local Variable Table)用于存放方法参数和方法内部定义的局部变量.以变量槽(Variable Slott)为最小单位. 一个Slot可以存放一个32位的数据类型,Java中占用32位以内的数据类型有boolean、byte、char、short、int、float;,对于 64位的数据类型((long
double)),虚拟机会以高位对齐的方式为其分配两个连续的Slot空间进行存储。



每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。Java栈也是线程私有的。

2、对于栈有两种异常情况

1)StackOverflowError

如果线程请求的栈深度大于栈可用深度,将抛出StackOverflowError异常;

2)OutOfMemoryError

如果虚拟机栈可以动态拓展,在拓展的时无法申请到足够的内存,将会抛出OutOfMemoryError异常

3、虚拟栈大小对程序的影响

在HotSpot 虚拟机中,可使用-Xss参数来设置虚拟栈的大小,这个值直接决定了函数调用可达的深度。

1)无参数、少成员变量

public class StackTest {
private int count=0;
public void recount()
{
count++;
System.out.println("stack 深度:"+count);
//System.out.println(Runtime.getRuntime().maxMemory()); //最大可用内存,对应-Xmx
recount();
}

@Test
public void test()
{
try {
recount();
} catch (Exception e) {

e.printStackTrace();
}
}
上面代码为一个递归调用,使用count记录递归的层次,修改JVM栈大小,当栈溢出时,执行结果如下:

栈大小设置为16m: -Xss16m stack最大深度:142954

栈大小设置为1m :-Xss1m stack 最大深度:5838

2)当方法带有多个参数和局部变量时:

public class StackTest2 {
private int count=0;
public void recount(long a,long b,long c)
{
long d =0;
long e =0;
long f =0; //占用栈空间
count++;
System.out.println("stack 深度:"+count);
recount(a,b,c);
}

@Test
public void test()
{
try {
recount(1L,2L,3L);
} catch (Exception e) {

e.printStackTrace();
}
}


-Xss16m stack 深度:116627

-Xss1m stack 深度:4354
这个结果相对测试1无局部变量、参数的情况,方法递归次数变少。这是因为方法的参数,局部变量越多,那么栈帧的局部变量表就会越大,栈帧会膨胀扩大内存以满足方法调用所需的参数传递,单个方法调用所需的栈空间也会随之增大。随着函数调用参数和局部变量的增加,单次函数调用对栈空间的需求也会增加,固定栈空间大小的情况下,可调用次数必然减少

4、局部变量空间重用

局部变量表的空间是可以重用的。

public void memory() {
long a = 0;
}
long b = 0;

public void memory2() {
long a = 0;
long b = 0;
}
方法1中变量a 的作用域仅限于方法体内,故在定义变量b时,a已经没有意义,故b可以重用a所在的空间。

方法2中同样定义了a、b两个变量,但他们的作用范围相同,不存在重用的可能。

编码不当导致栈空间无法释放又未被重用——栈溢出问题

如果一个变量被保存到局部变量表中,则系统垃圾回收时,就无法回收这部分空间。例如定义一个局部变量b,作用于方法体内,在GC调用时,变量b已经超过了它的作用范围(这里可以理解为已经完成了变量的使命,理应被回收变量所占空间),但最后GC却无法回收b变量所占空间,b变量仍在局部变量表中。

假设在变量失效后,在这个函数体内,又未能定义足够多的局部变量来复用该变量的表空间,则整个函数体内这块内存区域是不会被回收的。如果函数体执行非常费时同时又申请了较大的内存空间无法释放,则对系统性能将会造成较大压力。

解决办法

手动设置null值,帮助GC回收

合理的定义变量,灰常重要!!

四、程序计数器
Program Count Register,每一个线程都有一个独立的程序计数器,用于记录下一条要运行的指令,各线程间PC Register 互不影响。

程序计数器是线程的私有内存空间,每条线程都会拥有一个独立的程序计数器

五、本地方法栈

Native Method Stacks与java栈非常相似,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法。 本地方法是由C实现的,和Java栈一样,本地方法栈也是线程私有的,也可能抛出StackOverflowError和OutOfMemeryError异常,在HotSpot虚拟机中,对本地方法栈和虚拟机栈不做区分。

六、总结

名称

作用

范围

常见异常

程序计数器

记录当前线程的下一条命令

私有

虚拟机栈

存放方法的参数、局部变量

私有

StackOverFlowError

OutOfMemoryError

本地方法栈

存放本地方法的参数、局部变量

私有

同上



存放对象的实例

共享

OutOfMemoryError

方法区

存放常量、类定义信息

共享

OutOfMemoryError

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: