java中内存解析

1、Java内存模型:
编程时你需要考虑的不是内存的物理地址(memory address),而是一种逻辑上的内存模型。Java虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java栈和Java堆。

方法区是静态分配(static allocation)的,编译器将变量在绑定在某个存储位置上,而且这些绑定不会在运行时改变。Java方法区的一个重要部分,也是静态分配最典型的例子,是常数池,源代码中的命名常量、String常量和static 变量保存在其中。
Java Stack是一个逻辑概念,特点是后进先出,此外没有特别的要求。Java Stack并不代表任何特定的内存区间,也不限制它的实现方式。一个栈的空间可能是连续的,也可能是不连续的。最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的方法帧被弹出(pop)。栈分配存在一些局限:Java栈所处理的方法帧和局部变量,都将随着方法帧弹出而结束,显然局部变量无法从一个帧保持到下一个帧,被调方法的帧不可能比调用方法的寿命更长。因此,从数据保存来看,栈分配适用于由作用域决定生命周期的局部变量。
Java堆(Heap)堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。

2、有人问,基本类型变量和引用变量在哪里分配空间,这不是一个有效的问题。基本类型变量和引用变量都可能保存在stack或heap中,关键是看它们声明的位置——到底是域还是局部变量。参考,“引用在哪里分配空间?”
http://blog.csdn.net/yqj2065/archive/2008/11/25/3365726.aspx

3、“封装器”(wrapper)类。事实上,Integer、Double等包装类和String有着同样的特性:不变类。
String str = "abc"的内部工作机制很有代表性,这里我以Boolean为例,说明同样的问题。
不变类的属性一般定义为final,一旦构造完毕就不能再改变了。
于是private final boolean value;
而且由于Boolean对象只有有限的两种状态:true和false,将这两个Boolean对象定义为命名常量:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
这两个命名常量和字符串常量一样,在常数池中分配空间。
你注意到,Boolean.TRUE是一个引用,Boolean.FALSE是一个引用,而"abc"也是一个引用!(虽然我们经常把它混用地说成字符串对象。)

由于Boolean.TRUE是类变量(static)将静态地分配内存,所以需要很多Boolean对象时,并不需要用new表达式创建各个实例,完全可以共享这两个静态变量。其JDK中源代码是:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing)是JSE 5.0提供的新功能。自动装箱与拆箱的功能事实上是编译器帮了你一点忙,
Boolean b1 = 5>3;
等价于Boolean b1 = Boolean.valueOf(5>3);
//优于Boolean b1 = new Boolean (5>3);

static void foo(){
boolean isTrue = 5>3; //基本类型
Boolean b1 = Boolean.TRUE; //静态变量创建的对象
Boolean b2 = Boolean.valueOf(isTrue);//静态工厂
Boolean b3 = 5>3;//自动装箱(autoboxing)
System.out.println("b1 == b2 ?" +(b1 == b2));
System.out.println("b1 == b3 ?" +(b1 == b3));

Boolean b4 = new Boolean(isTrue);不宜使用
System.out.println("b1 == b4 ?" +(b1 == b4));//浪费内存、有创建实例的时间开销
}
这里b1、b2、b3指向同一个Boolean对象。

4、String的特殊性
由于String使用非常频繁,String常量也在常数池中静态分配内存。String str = "abc"的内部工作是这样的:
Java编译器首先扫描整个程序(简化地说),把其中所有的String常量收集起来,不同则分别“创建”一个String对象。
String str;其中str是一个引用。注意"abc"是一个引用。
因此:
String str1 = "abc";
String str2 = "abc";
String str3 = "ab"+"c";//同样
//str1 = "bcd";
System.out.println(str1==str2); //true
这里,str1、str2和 "abc"指向同一个String对象。它们都指向常数池中的那个对象。
==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回true。
如果str1指向同"bcd",则输出false。

再举一例:
public class A{
String x ="abc";
}
public class B{
String x ="abc";
}
public class User{
static void foo(){
A a = new A();
B b = new B();
System.out.println(a.x==b.x);
}
}
这里,System.out.println(a.x==b.x);也输出true。

String str1 = new String("abc"); 如何工作?

Boolean b4 = new Boolean(isTrue);不宜使用
一样,被我称为“傻瓜的代码”,由于new而在heap中创建一个String对象,并将其引用赋值给str1,
因此System.out.println(str1=="abc");输出false。

如果问你:String x ="abc";创建了几个对象?
准确的答案是:0或者1个。如果存在"abc",则变量x持有"abc"这个引用,而不创建任何对象。
如果问你:String str1 = new String("abc"); 创建了几个对象?
准确的答案是:1或者2个。(至少1个在heap中)

5、结论与建议:
(1)对语句String str = "abc";有人想当然地认为,创建了String类的对象str(注意,str常常被混用,称为“对象”,初学者最好简明地称它为“变量”,而不说“String类型的引用变量”)。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,变量str持有了String引用"abc"。罗罗嗦嗦的说法,我们声明了一个String类引用变量str,它被赋值了一个String的引用,这个引用是"abc"。(清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。信不信随你)


(2)对于String str = new String("abc");的代码,记住它是“傻瓜的代码”,既浪费内存、又有创建实例的时间开销。所谓“享元模式的思想”,你暂时别管它。

(3)比较两个对象的状态是否相等时,用你override后的Object.equals()方法;当测试两个引用变量是否指向同一个对象时,用obj1==obj2。

(4)对于immutable类如String,必要时设计一个配套的非不变类如StringBuffer;只要有可能,将类设计成不变类。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值