内存管理

java虚拟机运行时的数据区

程序计数器:
占内存空间很小,每条线程有一个独立的程序计数器(软件上的计数器,不是指硬件的计数器),用于记录执行到的位置,这样线程切换后可以恢复到正确的执行位置。 如果正在执行的是Native方法,则该计数器的值为0。

虚拟机栈:
虚拟机栈也是线程私有的,生命周期与线程相同,每个方法被执行时都会创建一个栈帧用于存储局部变量、操作数栈、动态链接、方法出口(也就是返回地址)等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
我们所说的堆栈中的栈指的就是虚拟机栈,更多的我们关注的是栈中的局部变表。局部变量表所需的内存空间在编译期完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,运行期间不会改变局部变量表的大小。那像ArrayList这样的对象,空间怎么分配呢,栈中只保存它的引用(大小固定的),指向的空间(大小不定)保存在堆中。
在JVM规范中,对该区域规定了两种异常:
1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛StackOverflowError异常。
2、如果虚拟机可以动态扩展(大部分虚拟机都可动态扩展),当扩展无法申请到足够的内存时(内存不足)会抛出OutOfMemoryError异常。
栈中保存的局部变量包括:
1、基本数据类型,如int、double等
2、保存引用变量(不是变量本身),如String、Set等对象
堆用来存放动态产生的数据
如new出来的对象(如刚才说的ArrayList)

堆:
堆是被所有线程共享的一块内存空间,堆的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,包括局部对象,如上面说的ArrayList, 只要是new出来的对象都在这里分配空间。

方法区:
也是线程共享的,用于存储类信息、常量、静态变量、编译后的代码。
方法区包含了运行时常量池,class文件除了有类的版本、字段、方法、接口等,还有一项信息是常量池,用于存放编译期生成的字面量和符号引用。
运行期间也可以将新的常量放入常量池,用的比较多的是String类中的intern()方法。
方法区也叫静态区!

本地方法栈:
虚拟机栈为JVM执行Java方法(也即字节码)服务,而本地方法栈则为JVM使用到的Native方法服务。两者非常相似,有些虚拟机直接把两者合二为一,本地方法栈也会抛StackOverflowError和OutOfMemoryError异常

判断两个对象是否指向同一个实例的唯一标准:==
如果a==b,那么a和b指向内存中的同一块地址,注意,是指向的地址相同,不是a、b本身相同,a、b本身不可能相同,比如A a1 = new A();A a2 = a1;则a1==a2,说明两者指向同一个实例,但a1是a1,a2是a2,它们是两个不同的变量,都要占4个字节,但指向的实例相同,实例在堆中得到分配(new出来的实例在堆分配)。
如果a1、a2是局部变量,那么它们本身在栈中,实例在堆中,如果它们是全局变量,则它们本身在堆中(因为它们所属的类被new之后会在堆中分配),指向的实例也在堆中。
Java八种基本数据类型(byte、int、double、float、long、boolean、short、char)都属于常量,如:
int a = 10;
如果a是全局变量,则a本身在堆中,如果a是局部变量,则a本身在栈中,但指向的10都在常量池中。
String不是基本数据类型,所以String内容被放到一个独立的字符串常量区中,但变量本身还是与变量所在的位置相关(全局在堆,局部在栈)。
hashcode相同的实例不代表是同一个实例,同样equals()方法判断相等的两个实例也不一定是同一个实例,这两个方法我们都可以重写。
String s1 = new String("abc");
String s2 = new String("abc");
s1.hashcode()==s2.hashcode();//true
s1.equals(s2);//true
但s1!=s2

注意:
一个类首先要被实例化才会有所谓的空间分配,类被实例化之后,类便在堆中分配到空间,类的所有成员变量(变量本身)自然也就在堆中分配到空间,但是,这些成员变量指向何处就要看成员变量的类型及其是如何创建的了,如果是通过new创建的,则变量指向堆,即数据存储在堆中,如果是基本数据类型,则变量指向常量区,如果是String str = "xxx";则str指向静态区。
类实例化之后,局部变量(变量本身)都在栈中得到分配,局部变量指向何处也要看变量是如何创建的,与全局变量一样。


例子1:

public class Test{

   public String s1 = "abc";

   public String method(){

      String s2 = "abc";

      return s2;

   }

   public static void main(String []args){

      Test test = new Test();

      if(test.s1==test.method())

         System.out.println("s1==s2");

   }

}

从打印结果看出,s1和s2指向同一块内存。
原因是Java在方法区(静态区)里有一块空间专门用来存放字符串常量,该区的字符串常量是所有对象实例共享的,不管哪一个实例,只要是通过String str = "xxx"来创建一个变量,都会首先到静态区去找,看是否存在这么一个字符串,如果存在,便将str指向该字符串所在的空间,如果以后对str从新赋值,则str指向另一个字符串,原来的字符串不会改变,如果原来的字符串没有其它引用指向它,则原来的字符串会被回收,所以,不管在哪,只要通过String str = "xxx"创建变量的,都指向同一个地方。但str本身存在哪就要看变量本身在哪声明,如上面的s1是类的全局变量,故s1本身存放在堆里(因为new Test的一个实例时,该实例会在堆中得到分配,自然其成员变量也在堆中分配空间,注意,s1本身在堆中分配,s1指向的空间却是静态区),s2是方法里的一个局部变量,所以s2本身存放在栈里,但s2也是指向静态区的。
注意:
利用new创建的字符串不再放在字符串常量池中,如:
String str1 = "abc";//放在常量池中
String str2 = new String("abc");//堆中
String str3 = "a"+new String("bc");//堆中
故:
str1 == str2;//返回false
str1 == str3;//返回false
通过 intern()方法可以主动把一个字符串对象放到常量池中,所以
str2 = str2. intern();
str1 == str2;//返回true

JDK1.7以后switch支持字符串作为匹配参数,包括常量池的字符串和new创建的字符串!!
例子2:

String MESSAGE="taobao";

String a = "tao"+"bao";

String b = "tao";

String c = "bao";

a ==  MESSAGE;//返回true
b + c == MESSAGE;//返回false
解释:
Message、a、b、c的值在编译的时候都确定了,所以它们都被存放到字符串常量区中,故a == Message返回true。但(b+c)的值要到运行时才知道,因为b和c可能在运行的时候被赋予新的值,所以(b+c)被放到堆中,即使是d=b+c;,d也会被放到堆中,所以d==Message返回false。
如果用final修饰b和c,则b+c==Messagel返回true。

版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://my.oschina.net/u/1987489/blog/491318

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值