Java虚拟机内存模型

jopen 4年前

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  */

虚拟机栈在运行时使用了一种叫做栈帧的数据结构保存上下文数据,在栈帧中,存放了方法的局部变量表、操作数栈、动态链接方法和返回地址等信息。每个方法的调用都伴随着栈帧的入栈操作,方法的返回则表示栈帧的出栈操作。方法调用时,方法的参数和局部变量越多,局部变量表就越大,栈帧相应变大以满足方法调用所需传递的信息。因此,单个方法调用所需的栈空间大小也会比较多
Java虚拟机内存模型
结论:使用-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)  */
来自:http://my.oschina.net/aptx4869/blog/323369