JVM内存区域

主要参照《深入理解Java虚拟机》,其介绍的比较生硬,没有例子说明,很难记住或读懂。本博文参照了其他人的博客,总结于此。

粗分

Java内存分为堆内存和栈内存,所指的栈就是下边介绍的虚拟机栈,或者是虚拟机栈中的局部变量表。堆来存放对象,栈来存放基本类型的数据

细分

Java虚拟机运行时数据区

  1. 程序计数器
    程序计数器与CPU中的PC功能类似,可以看作为当前线程所执行字节码的行号指示器在CPU中PC计算执行下一条指令的地址),来选择下一条需要执行的字节码指令,如分支、循环、跳转、异常处理、线程回复等。
    Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,在任何一个时刻,一个处理器只能执行一条线程中的指令。因此,为了线程切换后能够回复到正确的执行位置,每条线程需要一个单独的程序计数器,互不影响,故在此类内存区域内,线程是私有的。
    如果线程执行的是一个Java方法,计数器记录的是虚拟机将要执行的字节码指令的地址;如果是Native方法,计数器为空(Undefined)。

  2. 虚拟机栈
    虚拟机栈也是线程私有的,与线程生命周期相同。其描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表操作数栈动态链接方法出口等(用于进行方法的操作执行)。每个方法从调用到执行完成,都会对应一个栈帧在虚拟机栈中入栈和出栈的过程。
    局部变量表存放编译期可知的各种基本数据类型对象引用returnAddress类型

    • 基本数据类型:boolean、char、byte、int、long、float、double;
    • 对象引用:引用类型,不同于对象本身,可能是一个指向对象起始地址的引用指针、指向一个代表对象的句柄或其他与此对象相关的位置;
    • returnAddress类型:指向一条字节码指令的地址。
      其中64位长度的long和double类型的数据占用2个局部变量表(Slot),其余都占用1个。局部变量表所需的内存空间在编译期完成分配,当进入一个方法时,方法中局部变量表的大小是确定的,在方法运行期不会改变。
  3. 本地方法栈
    与虚拟机栈类似,区别在于虚拟机栈执行的是Java方法,本地方法栈执行的是Native方法。

  4. Java堆
    堆是JVM中最大的一块内存,所有线程共享,与虚拟机的生命周期相同。
    按JVM规范中描述的:所有对象的实例以及数组都要在堆上分配。但随着JIT编译器的发展和逃逸分析技术的成熟,所有对象都在堆上分配,并不绝对。Java堆是垃圾收集器管理的主要区域。

  5. 方法区/静态区
    与堆一样,该内存区域线程共享。
    它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(即一般在运行期固定不变的信息或变量)。

  6. 运行时常量池
    运行时常量池是方法区的一部分。
    Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池,用于存放编译期生成的各种字面量和符号引用字面量:基本数据类型构造包装器类、创建String对象、数组等;符号引用:文本形式,如类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。这部分内容将在类加载后进入方法区的常量池中存放。
    运行时常量池相对于Class文件常量池的另一个特征是具有动态性,可以不在编译期确定,即并非预先置入Class文件中的常量池的内容才能进入方法区的运行时常量池,运行期间也可以将新的常量放入池中,如String的intern()方法。(关于常量池的应用在后续补上

举例说明

主要参照:http://blog.csdn.net/lyerliu/article/details/6311709

 public class AppMain //运行时,jvmappmain的信息都放入方法区
{
    public static void main(String[] args)  //main方法本身放入方法区。
    {
         Sample test1 = new Sample( " 测试1 " );   //test1是对象引用,所以放到栈区里,Sample是自定义对象应该放到堆里面
          Sample test2 = new Sample( " 测试2 " );

         test1.printName();
         test2.printName();
    }
}


public class Sample //运行时,jvmappmain的信息都放入方法区
{
     /** 范例名称 */
     private String name; //new Sample实例后,name引用放入栈区里,name对象放入堆里

      /** 构造方法 */
     public Sample(String name)
     {
          this.name=name;
     }

     /** 输出 */
     public void printName()   //print方法本身放入 方法区里。
     {
         System.out.println(name);
     }
}

这里写图片描述

执行过程:

  • JVM读取AppMain.class中的二进制数据;
  • 将AppMain类的类信息加载入方法区;
  • 定位main方法,加载入栈区,开始执行;
  • 遇到Sample类,加载Sample类信息到方法区;
  • test1为对象引用,放到栈区;
  • 在堆区创建一个对象实例,该对象实例持有指向方法区中的Sample类信息的引用,即指向方法区中Sample类型的内存地址,用于执行类中的方法。

例子其中没有涉及到基本数据类型的存储问题,在此根据查的资料补上。

基本数据类型不涉及类,直接存在栈中,栈中的数据可以共享,但与对象不同。参照:http://dong-shuai22-126-com.iteye.com/blog/1341252(其中关于String常量池的地方有错误,不是存在栈区,而是在方法区)。直接将其中关于基本数据类型的介绍搬于此:

基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。
另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
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与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

其中还有个问题感觉介绍的有点片面:
Java中变量分为静态变量,实例变量,临时变量。那么各种变量具体保存在JVM中的何处呢?
静态变量:位于方法区。
实例变量:作为对象的一部分,保存在堆中。
临时变量:如果为基本数据类型,存放在栈中;如果为对象实例,那就会存放在堆中(不能直接说:保存于栈中,栈随线程的创建而被分配)。

总结

说到JVM内存,主要考虑堆、栈、方法区。

  • 堆区:存放对象实例和数组,每个对象都包含一个与之对应的class信息(便于去对象实例进行相关操作);
  • 栈区(局部变量表):存储基本数据类型、对象引用、returnAddress类型;每个线程的栈中数据都是私有的其他栈不能访问;
  • 方法区:存放类信息、常量、static变量等;

堆与栈的区别:

  • 栈与堆都是Java用来在RAM中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
  • 栈的优势是,存取速度快 ,仅次于直接位于CPU中的寄存器;缺点是,存在栈中的数据大小与生命周期是确定的,缺乏灵活性;另外,栈数据可以共享。
  • 堆的优势是可以动态地分配内存大小,生命周期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据;缺点是,由于要在运行时动态分配内存,存取速度较慢。

可能理解的还不够透彻,有些地方难免有偏差,欢迎大家指正,共同学习!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值