JVM内存模型总结

 1、硬件内存结构

  正如上图所示,经过简化CPU与内存操作的简易图,实际上没有这么简单,这里为了理解方便,我们省去了南北桥并将三级缓存统一为CPU缓存(有些CPU只有二级缓存,有些CPU有三级缓存)。

  就目前计算机而言,一般拥有多个CPU并且每个CPU可能存在多个核心,多核是指在一枚处理器(CPU)中集成两个或多个完整的计算引擎(内核),这样就可以支持多任务并行执行,从多线程的调度来说,每个线程都会映射到各个CPU核心中并行运行。在CPU内部有一组CPU寄存器,寄存器是cpu直接访问和处理的数据,是一个临时放数据的空间。一般CPU都会从内存取数据到寄存器,然后进行处理,但由于内存的处理速度远远低于CPU,导致CPU在处理指令时往往花费很多时间在等待内存做准备工作,于是在寄存器和主内存间添加了CPU缓存,CPU缓存比较小,但访问速度比主内存快得多,如果CPU总是操作主内存中的同一址地的数据,很容易影响CPU执行速度,此时CPU缓存就可以把从内存提取的数据暂时保存起来,如果寄存器要取内存中同一位置的数据,直接从缓存中提取,无需直接从主内存取。需要注意的是,寄存器并不每次数据都可以从缓存中取得数据,万一不是同一个内存地址中的数据,那寄存器还必须直接绕过缓存从内存中取数据。所以并不每次都得到缓存中取数据,这种现象有个专业的名称叫做缓存的命中率,从缓存中取就命中,不从缓存中取从内存中取,就没命中,可见缓存命中率的高低也会影响CPU执行性能,这就是CPU、缓存以及主内存间的简要交互过程,总而言之当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存(当然如果CPU缓存中存在需要的数据就会直接从缓存获取),进而在读取CPU缓存到寄存器,当CPU需要写数据到主存时,同样会先刷新寄存器中的数据到CPU缓存,然后再把数据刷新到主内存中。

2、java运行时数据区

 

  2、1、JVM管辖的内存大致分为线程隔离的:、本地方法栈、程序计数器。线程共享的:堆、方法区。在JVM启动时创建,关闭时全部回收。


  2、2、栈

     i)、存放基本类型的变量数据和对象的引用变量(变量本身存放在堆和常量池中)。
     ii)、java系统必须知道存储在栈内所有项的确切生命周期(故变量的垃圾回收由系统处理),以便上下移动栈指针,相比堆更高效,但限制了程序的灵活性。
     iii)、可以数据共享(共享的是值,改变一个变量不会影响另一个变量)
        int c = 1;int d =1;//c,d共享栈数据1
        d = 2;//如果栈中没有2这个值(如果有就直接引用共享),新建值并将d重新指向它。

  2、3、堆

    i)、存放由new操作符创建的对象和数组(‘可以但不必须’将其引用存放在栈中,方便快速访问)。
    ii)、运行时动态分配内存(速度慢于栈的主要原因),并由gc管理。
    iii)、堆中的垃圾回收由gc处理(用户不能干预,即便调用System.gc()也只是‘希望执行’垃圾回收。因为gc是为所有java应用进程服务且gc的执行会影响所有java应用进程,不特殊对待某个进程)。


  2、4、方法区

    i)、位于堆上的静态存储区,存储被装载到JVM中的class信息。如存放加载过的类信息,常量,静态变量,及JIT编译后的代码(类方法)等数据。



  2、5、常量池

     jdk1.7后从方法区移动到了堆中,存放类、方法、接口等中的字面常量和符号引用。 每个常量池在编译期确定下来,并保存到.class文件中。可以数据共享。


  特殊的String对象
    String a1 = "123"; //这一句创建了1个字符串常量对象(常量池中)。
    String a2 = "123"; //这两句还是1个对象。a2也指向a1的字符串常量对象“123”。

    String b1 = new String("123"); //这一句本身应该创建两个对象(先创建常量对象“123”,再用它做参数用new在堆中创建一个新对象)。但因为字符串常量对象“123”已存在(常量池),所以此时只创建1个对象(堆中)
    String b2 = new String("123"); //此时因为“123”已存在常量池中,故只用new操作符新建一个对象。

    System.out.println(a1 == a2); //运算符 == 比较引用地址
    System.out.println(b1 == b2);

    对于 "123"           --在常量池中创建字符串常量对象“123”(如果常量池中已有该对象就直接引用而不再创建)。共一个对象,位于常量池中。
    对于 new String("123")       --先在常量池中创建字符串常量对象“123”,再在堆中用new操作符创建新对象(new操作符每次都新建不同内存地址的对象)。共两个对象,分别存储于常量池、堆中。

 

3、垃圾回收

    i)、处理位于java逻辑堆上的垃圾对象。

    ii)、采用基于“标记-清除”、“停止-复制”算法的“自适应”的垃圾回收技术。进行垃圾回收时会暂停程序的运行。

    iii)、当对象失去所有引用成为垃圾后,可能不被垃圾回收。

    iiii)、无论“垃圾回收”还是“终结”,都不保证能发生,如果JVM并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以回复内存。

    iiiii)、finalize()正确用法是,在对象即将被回收时,用来做一些善后处理。

    iiiiii)、System.gc()用于强制进行垃圾回收动作。但并不能保证垃圾回收动作的执行,顶多增加垃圾回收动作发生的可能性。垃圾回收动作不受用户干预,因为gc服务于JVM中的所有进程,不为某个进程特殊处理。

 

      int[] ary = {1,2,3}; //当ary超过作用域后,变量ary将立即在栈中被系统回收并可另作他用。它所指向的数组对象被gc‘标记-清除’,但对象依然存在堆中,等到一个(不确定)时间被gc回收。

4、对象创建的总结

    java栈用来存放基本类型的变量数据和对象的引用变量,但不存放对象内容;
    java堆存放使用new关键字创建的对象和数组对象;
    特殊情况是字符串这个包装类:其引用是存放在栈里的,而对象内容位置由创建方式来决定(常量池、堆),用“”在编译期创建的存放在字符串常量池中,用new操作符在运行时创建的存放在堆中。

 

posted @ 2017-07-28 23:29  衿沫青冥  阅读(176)  评论(0编辑  收藏  举报