Java的堆栈分析

来源:互联网 发布:c语言的函数 编辑:程序博客网 时间:2024/04/29 02:19

1Java内存模型:

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

 

方法区是静态分配(static allocation)的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。Java方法区的一个重要部分,也是静态分配最典型的例子,是常数池,源代码中的命名常量、String常量和static变量保存在其中。

Java Stack是一个逻辑概念,特点是后进先出,此外没有特别的要求。JavaStack并不代表任何特定的内存区间,也不限制它的实现方式。一个栈的空间可能是连续的,也可能是不连续的。最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的方法帧被弹出(pop)。栈分配存在一些局限:Java栈所处理的方法帧和局部变量,都将随着方法帧弹出而结束,显然局部变量无法从一个帧保持到下一个帧,被调方法的帧不可能比调用方法的寿命更长。因此,从数据保存来看,栈分配适用于由作用域决定生命周期的局部变量。

Java(Heap)堆分配(heapallocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。

 

2、有人问,基本类型变量和引用变量在哪里分配空间,这不是一个有效的问题。基本类型变量和引用变量都可能保存在stackheap中,关键是看它们声明的位置——到底是域还是局部变量。参考,“引用在哪里分配空间?”

http://blog.csdn.net/yqj2065/archive/2008/11/25/3365726.aspx

 

3、“封装器”(wrapper)类。事实上,IntegerDouble等包装类和String有着同样的特性:不变类。

String str = "abc"的内部工作机制很有代表性,这里我以Boolean为例,说明同样的问题。

不变类的属性一般定义为final,一旦构造完毕就不能再改变了。

于是private final boolean value;

而且由于Boolean对象只有有限的两种状态:truefalse,将这两个Boolean对象定义为命名常量:

public static final Boolean TRUE = new Boolean(true);

public static final Boolean FALSE = newBoolean(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 voidfoo(){

        booleanisTrue = 5>3;  //基本类型

        Booleanb1 = Boolean.TRUE; //静态变量创建的对象

        Booleanb2 = Boolean.valueOf(isTrue);//静态工厂

        Booleanb3 = 5>3;//自动装箱(autoboxing)

       System.out.println("b1 == b2 ?" +(b1 == b2));

       System.out.println("b1 == b3 ?" +(b1 == b3));

      

        Booleanb4 = new Boolean(isTrue);////不宜使用

       System.out.println("b1 == b4 ?" +(b1 == b4));//浪费内存、有创建实例的时间开销

    }

这里b1b2b3指向同一个Boolean对象。

 

4String的特殊性

由于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

这里,str1str2 "abc"指向同一个String对象。它们都指向常数池中的那个对象。

==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回true

如果str1指向同"bcd",则输出false

 

再举一例:

public class A{

    String x="abc";}

public class B{

    String x="abc";}

public class User{

    static voidfoo(){

        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 = newString("abc");的代码,记住它是“傻瓜的代码”,既浪费内存、又有创建实例的时间开销。所谓“享元模式的思想”,你暂时别管它。

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

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

原创粉丝点击