java 虚拟机内存模型

java 虚拟机内存模型是java 程序运行的基础。jvm虚拟机将内存数据 分为程序计数器, 虚拟机栈,本地方法栈,java堆 和方法区等部分。

程序计数器用于存放下一条的指令,虚拟机栈和本地方法栈用于存放函数调用堆栈信息,java堆用于存放java运行时所需的对象等数据,方法区用于存放程序的类元数据信息。


1. 程序计数器(Program Counter Degister)

       程序计数器是一块很小的内存空间。由于java 是支持线程的语言,当线程数量超过CPU的数量时,线程之间根据时间片轮询来抢夺CPU资源。对于单核CPU来说,任一时刻只能运行一个线程,其他线程必须被切换出去。为此,每一个线程必须有一个独立的程序计数器,用于记录下一条运行的指令。各个线程之间的程序计数器互不影响,独立工作,是一块线程私有的内存空间。

     如果当前线程正在执行一个java方法,程序计数器记录正在执行的java字节码地址,如果现在执行的是一个native方法,程序计数器为空。


2.java虚拟机栈

       java虚拟机栈也是线程私有的内存空间,它和java线程在同一时间创建,它保存方法的局部变量,部分结果,并参与方法的调用和返回。

       java虚拟机规范允许java栈的大小是动态的或者固定的。java虚拟机规范中定义了两种异常与栈空间有关:StackOverflowError 和 OutOfMemoryError。

      如果线程在计算过程中,请求的栈深度大于可用的最大栈深度,则抛出StackOverflowError。如果java栈可以动态扩展,而在扩展栈的时候没有足够的内存空间

空间来支持扩展,则会抛出OutOfMemoryError。

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

      以下代码展示了一个递归的应用。计数器count记录了递归的层次,此递归没有出口一定可以栈溢出,在栈溢出的时候打印栈深度。

      public class TestStack {

    private static int count = 0;
    public static void recursion(){
        count++;
        recursion();
    }
    
    public static void main(String[] args){
        try{
            recursion();
        }catch(final Throwable e){
            System.out.println("deep of stack is "+count);
            e.printStackTrace();            
        }
    }
}

上述程序打印结果:deep of stack is   12057
                                    java.lang.StackOverflowError

          如果想要程序支持更深的栈调用,则可以使用-Xss1M来运行程序。

         虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据。在栈帧中,存放了方法的局部变量表,操作数栈,动态连接方法和返回地址等信息。

         每一个方法的调用都伴随这栈帧的入栈操作,方法的返回则伴随着栈帧的出栈操作。如果方法调用时方法的参数和局部变量相对较多,那么栈帧中局部变量表就会变大

 栈帧会膨胀以满足方法调用所需的传递信息。单个方法调用所需的栈的空间大小也会增大。看一下代码结果:

public class TestStack {

    private static int count = 0;
    public static void recursion(long a,long b,long c){
        long e = 0L,f = 0L,d = 0L;
        count++;
        recursion(a,b,c);
    }
    
    public static void main(String[] args){
        try{
            recursion(1L,2L,3L);
        }catch(final Throwable e){
            System.out.println("deep of stack is "+count);
            e.printStackTrace();            
        }
    }
}

结果:deep of stack is 4492
            java.lang.StackOverflowError

      可以看出,随着调用函数参数和局部变量的增加,单次函数调用对栈空间的需求也会增加。(函数调用次数由12057变为4492)

3. 本地方法栈

        本地方法栈和虚拟机栈功能类似,java虚拟机栈用于管理java函数的调用,而本地方法栈用于管理本地方法的调用。本地方法并不是java实现的,而是C实现的。

4.  java堆

        java堆是java运行时内存中最重要的部分,几乎所有的对象和数组都是堆中的分配空间中。java堆分为新生代和老年代两个部分,新生代用于存放刚刚产生的对象和年轻的对象,如果对象一直没有被回收,生存的足够长,老年对象就会被移入老年代。

       新生代又可进一步细分为eden,survivor space() 和survivor space1()。

       为了更方便的理解对象在内存中的分配方式,结合下例。

       public class TestHeapGC {
               public static void main(String[] args){
                      byte[] b1 = new byte[1024*1024/2];
                       byte[] b2 = new byte[1024*1024*8];
                        b2 = null;
                        b2 = new byte[1024*1024*8];
        
                         System.gc();
               }
        }

    第一次在注释System.gc()。结果:

     

      如果加上System.gc(),结果

   

    可以看出在  full gc  之后,新生代对象被清空,未被回收的对象全部被移入老年代。

5.方法区

      方法区也是jvm内存区中一块重要的内存区域,与堆空间类似,它也是被所有线程共享的,方法区主要保存的信息是类的元数据。

     方法区中最为重要的是类的类型信息,常量池,域信息,方法信息。

     类型信息包括类的完整名称,父类的完整名称,类型修饰符和类型的直接接口类表。常量池包括这个类方法,域等信息所引用的常量信息。

     域信息包括域名城,域类型和域修饰符。方法信息包括方法参数,返回类型,方法名称,方法修饰符,方法字节码,操作数栈,方法帧栈的局部变量区大小以及异常表。

      方法区是一块永久区,独立于java堆的内存空间,也是可以被GC回收的。

     对永久区的GC回收包括两个方面,1.GC对永久区常量池的回收,2.对类元数据的回收。

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值