首页 > 代码库 > Java虚拟机内存模型

Java虚拟机内存模型

1)程序计数器:线程私有

当线程数量超过CPU数量时,线程之间根据时间片轮询抢夺CPU资源,对于单核CPU来说,每一个时刻,只能有一个线程在运行,而其他线程必须被切换出去。因此,每个线程都有一个独立的程序计数器,用于记录下一条要运行的指令,各个线程之间的计数器互不影响,是一块线程的私有内存空间。当一个线程正在执行一个Java方法时,程序计数器记录正在执行的Java字节码地址,如果执行的是native方法,则计数器为空
2)Java虚拟机栈:线程私有

同Java线程同时间创建,用于保存方法的局部变量,部分结果,并参与方法的调用和返回。Java虚拟机定义了两种与栈空间有关的异常:StackOverflowError,OutOfMemoryError。当请求的栈深度大于最大可用的栈深度,抛出StackOverflowError。如果栈可以动态扩展,扩展栈的过程中,没有足够的内存空间来支持栈的扩展(系统内存不足?),则抛出OutOfMemoryError。以下为一个抛出java.lang.StackOverflowError的实例

public class TestStack{
    private static int count=0;
    public static void recursion(long a,long b,long c){
        long d=0;
        long e=0;
        long f=0;
        count++;
        recursion(a,b,c);
    }
    public static void main(String []args){
        try{
            recursion(1L,2L,3L);
        }catch(Throwable e){
            System.out.println("count:"+count);
            System.out.print(e);
        }
    }
    public void recursion1(long a,long b,long c){
        long d=0;
        long e=0;
        long f=0;
        count++;
        recursion1(a,b,c);
    }
}
/*
直接运行
输出:
    count:4927
使用-Xss来扩大栈空间,运行 java -Xss1m TestStack
输出:
    count:27590
*/

虚拟机栈在运行时使用了一种叫做栈帧的数据结构保存上下文数据,在栈帧中,存放了方法的局部变量表、操作数栈、动态链接方法和返回地址等信息。每个方法的调用都伴随着栈帧的入栈操作,方法的返回则表示栈帧的出栈操作。方法调用时,方法的参数和局部变量越多,局部变量表就越大,栈帧相应变大以满足方法调用所需传递的信息。因此,单个方法调用所需的栈空间大小也会比较多
在此输入图片描述
结论:使用-Xss来扩大栈空间,增加栈空间大小后,函数调用深度上升。函数的局部变量越多,栈帧就越大,单次函数调用对栈空间的需求也会增加,函数嵌套调用次数减少

局部变量表的空间是可重用的,对于一个方法所分配的最大局部变量表的容量,可以使用jclasslib工具来查看

public class Test1{

    public static void main(String []args){
        test3();
    }
    public static void test1(){
        {
            byte[] b=new byte[6*1024*1024];
        }
        //此时b仍然在该栈帧的变量表中,
        //GC根可以引用到该内存块,又未能有足够多的局部变量来服用该内存块,
        //因此不会被回收[1]
        System.gc();
        System.out.println("first explict gc over");        
    }
    public static void test2(){
        {
            byte[] b=new byte[6*1024*1024];
            b=null;//帮助系统GC
        }
        //被回收[2]
        System.gc();
        System.out.println("first explict gc over");    
    }
    public static void test3(){
        {
            byte[] b=new byte[6*1024*1024];
        }
        int a=0;//服用b的内存块,GC根无法找到b,因此被回收[3]
        System.gc();
        System.out.println("first explict gc over");    
    }
}
/*
java -verbose:gc Test1
--[1]
[GC 224K->134K(5056K), 0.0027563 secs]
[Full GC 134K->134K(5056K), 0.0177525 secs]
[Full GC 6278K->6278K(11204K), 0.0151799 secs]
first explict gc over

--[2]
[GC 224K->134K(5056K), 0.0027134 secs]
[Full GC 134K->134K(5056K), 0.0178466 secs]
[Full GC 6278K->134K(11204K), 0.0152811 secs]
first explict gc over

--[3]
[GC 224K->134K(5056K), 0.0027211 secs]
[Full GC 134K->134K(5056K), 0.0174787 secs]
[Full GC 6278K->134K(11204K), 0.0147169 secs]
first explict gc over
*/

3)本地方法栈:
与Java虚拟机栈功能相似,Java虚拟机栈用于管理Java函数调用,而本地方法栈用于管理本地方法的调用,有C实现。在Hot Spot虚拟机中不区分本地方法栈和Java虚拟机栈,因此,同样也会抛出StackOverflowError,OutOfMemoryError异常

4)Java堆:Java运行时内存中最重要的部分
分配运行时所有的对象和数组。分为新生代(eden,survivor space0[s0,from space],survivor space1[s1,to space])和老年代。

5)方法区:线程共享
方法区存放的是类的类型信息,常量池,域信息,方法信息等大部分来自class文件的信息。在Hot Spot虚拟机中,方法区也叫做永久区,是一块独立于Java堆的内存空间,同样也可以被GC回收,只是表现和Java堆不同

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
public class PermGenGC{
    public static void main(String []args){
        List<String> list=new ArrayList<String>();
        int i;
        for(i=0;i<Integer.MAX_VALUE;i++){
            //如果常量池中存在当前String,则返回池中对象,如果不存在,则先将String加入到常量池,再返回池中对象引用
            list.add(String.valueOf(i).intern());//加入到常量池
        }
    }
}
/*
java -XX:PermSize=2m -XX:MaxPermSize=4m PermGenGC

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at PermGenGC.main(PermGenGC.java:7)

java -XX:PermSize=2m -XX:MaxPermSize=4m -XX:+PrintGCDetails PermGenGC

[GC [DefNew: 896K->64K(960K), 0.0043064 secs] 896K->180K(5056K), 0.0050202 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC [DefNew: 919K->0K(960K), 0.0031302 secs] 1035K->236K(5056K), 0.0039759 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC [DefNew: 778K->0K(960K), 0.0029668 secs] 1015K->390K(5056K), 0.0036493 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 896K->0K(960K), 0.0030697 secs] 1286K->619K(5056K), 0.0037499 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 896K->0K(960K), 0.0033067 secs] 1515K->964K(5056K), 0.0040117 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC [Tenured: 964K->581K(4096K), 0.0595687 secs] 1665K->581K(5056K), 
[Perm: 4095K->4095K(4096K)], 0.0610942 secs] [Times: user=0.06 sys=0.00, real=0.06 secs]
[Full GC [Tenured: 581K->581K(4096K), 0.0591349 secs] 581K->581K(5056K), 
[Perm : 4095K->4095K(4096K)], 0.0636965 secs] [Times: user=0.06 sys=0.01, real=0.06 secs]
Exception in thread "main" 
[Full GC [Tenured: 581K->236K(4096K), 0.0289138 secs] 596K->236K(5056K), 
[Perm : 4095K->360K(4096K)], 0.0332241 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at PermGenGC.main(PermGenGC.java:8)
*/

Java虚拟机内存模型