这两天看了关于JVM内存相关的一些资料,为了方便以后查阅,在此留下一篇博文,顺便加深理解。另外,可参考:http://web.cutt.com/article/1740661967

    根据《分布式JAVA应用、基础与实践》一书中的说明,JVM规范将内存空间划分为方法区、堆、本地方法栈、PC寄存器及JVM方法栈。如下图所示:

java 空闲 java 空间_数据

    方法区:存放了要加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息。方法区域是全局共享的,在一定条件下它也会被GC,当方法区要使用的内存超过其允许的大小时,会报出OutOfMemory的错误信息。

    本地方法栈:用户支持native方法的执行,存储了每个native方法调用的状态,在Sun JDK的实现中本地方法栈和JVM方法栈是同一个。

    PC寄存器和JVM方法栈:每个线程均会创建PC寄存器和JVM方法栈,PC寄存器占用的可能为CPU寄存器或操作系统内存,JVM方法栈占用的为操作系统内存,JVM方法栈为线程私有。当方法运行完毕时,其对应的栈帧所占用的内存也会自动释放。

    下面,从java涉及到的类型来描述。

    1、java虚拟机内存原型

    寄存器:不属于内存,我们在程序中无法控制。

    栈:存放基本类型的数据和对象的引用,但对象本身是放在堆中的。

    堆:存在对象。

    静态域:存在在对象中用static定义的静态成员。

    常量池:存放常量。

    2、常量池

    常量池指的是在编译期间被确定,并被保存在已编译的class文件中的一些数据。在程序执行的时候,常量池会存储在方法区中。

    3、java内存中分配的栈

    栈的基本单位是帧(或栈帧)。每当一个java线程运行的时候,java虚拟机会为该线程分配一个java栈。java栈中的所有数据都是私有的,其他线程都不能访问该线程的栈数据。当在一段代码块定义一个变量时,java就在栈中为这个变量分配内存空间,当该变量推出该作用域后,java会自动释放掉为该变量所分配的内存空间。很明显,这部分对应JVM方法栈

    4、java内存中分配的堆

    java虚拟机中的堆用来存放由new创建的对象和数组。

    注意:引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。实际上,栈中的变量指向堆内存中的变量,这就是java中的指针!

    堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

    栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

    栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

    int a=3; int b=3; 编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3.接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3.这样,就出现了a与b同时均指向3的情况。

    这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响 到b的值。

    要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。