Java栈与堆又一篇烂文



------
我说Java栈与堆 及其他

看见CSDN论坛中的这个东西【Java栈与堆一篇好文!! [推荐]】
http://topic.csdn.net/u/20081123/12/f70f1632-24be-4caa-bc20-29cf8267afab.html
 ,我发了一个帖子“为什么 《Java栈与堆》是垃圾”,
http://topic.csdn.net/u/20081124/22/99347851-c05d-4572-86d7-e74f949e7282.html
但是看见那个帖子后面的源源不断的跟帖,很为up的人难过。
按照该文的格式重写了一下。

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 楼 kokobox 的回复:
结束不了了

------
1.你说的问题和JAVA内存模型没有任何关系。JAVA内存模型是指JVM实现多线程共享变量之间的内存复制方法而不是你所说那种内存布局。
2.你自己也没有真正明白变量,引用和对象的实质."abc"是一对象,永远不会是引用.
如果问你:String str1 = new String("abc"); 创建了1或2个对象完全错误.
如果说创建了1个字符串对象,它绝对不会创建2个字符串对象.如果仅仅说创建了几个对象,一个字符串对象内部包含N个其它对象,但1个或2个是
完全错误的.

其它的不想说了.讨论这些问题,如果不对JVM有深切的了解,那就没有意义.靠想象可能会有一万种观点,而真正正确的其实只有一个.但我目前在CSDN
还没有看到真正深入了解JVM的人.
------
引用 4 楼 axman 的回复:
1.你说的问题和JAVA内存模型没有任何关系。JAVA内存模型是指JVM实现多线程共享变量之间的内存复制方法而不是你所说那种内存布局。
2.你自己也没有真正明白变量,引用和对象的实质."abc"是一对象,永远不会是引用.
如果问你:String str1 = new String("abc"); 创建了1或2个对象完全错误.
如果说创建了1个字符串对象,它绝对不会创建2个字符串对象.如果仅仅说创建了几个对象,一个字符串对象内部包含N个其它对象,但1个或2个是


------
对于LZ说的:Java编译器首先扫描整个程序(简化地说),把其中所有的String常量收集起来,不同则分别“创建”一个String对象。

编译器是否这样做的我不清楚。
但确实对String做了很多优化。
String str1 = "abc"; 
String str2 = "a" + "b" + "c";

str1 == str2 会返回true;

但下面这句不认同:
String str;其中str是一个引用。注意"abc"是一个引用。

java中数据类型分为 基本类型和引用类型。

String str;str是引用,但"abc"不会是引用。
------
我平时都是"abc"是字符串对象的。这里不是要严格一点吗?
所以我才说"abc"是String的引用。

String str;“str是引用”,我喜欢严格地说str是一个变量,而引用是变量的值。
------
String str;其中str是一个引用。注意"abc"是一个引用。

改正:
String str;其中str是一个引用变量。注意"abc"是一个引用。
------
支持楼主..受益颇多...
------
如果楼主真的为大家着想就把那个你觉得错的地方改成正确的,这样比到处说别人的不好会好些
------
无所事事,飘过....
------
路过就看看!
------
支持楼上的..楼主强人就把那篇错的地方改正 来一篇?不胜感激...
------
再一次说明你确实没有理解什么叫引用。不客气地说以你现在的能力还描述不清楚它们。
------
“public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
这两个命名常量和字符串常量一样,在常数池中分配空间。 ”
这是明显错误的,常量池不会把类库里的static final的东西都放进去。
------
驳斥是没啥问题的
但说垃圾就不好了~
------
static final常量是编译期常量,即使程序没有运行它已经在执行码中占用了空间.所以不可能在运行期的常量池中的.
(static final表达式不一定,看是否为常量表达式)

不想再说了.不深入了解JVM的讨论这些问题没有任何意义,不是靠一知半解加想象的.比如JMM这些JVM的基础的基础也没有弄清.

如果说连机械原理也不了解来讨论发动机工作时的压缩比,有什么意义呢?

我虽然不能一个人写出一个JDK,但给我一批人和一些时间,我可以让一个团队写出一个JVM原型机.就象我一个人不能写出操作系统,
但我能让一个团人写出操作系统模型,虽然不能真正商用,但至少我了解操作系统的每个模块的实现原理.这时你再来讨论系统调用,
作业高度才有意义.
------
引用 17 楼 axman 的回复:
static final常量是编译期常量,即使程序没有运行它已经在执行码中占用了空间.所以不可能在运行期的常量池中的.
(static final表达式不一定,看是否为常量表达式)

不想再说了.不深入了解JVM的讨论这些问题没有任何意义,不是靠一知半解加想象的.比如JMM这些JVM的基础的基础也没有弄清.

如果说连机械原理也不了解来讨论发动机工作时的压缩比,有什么意义呢?

我虽然不能一个人写出一个JDK,但给我一批人和一些时间,我可以让一…

------
高人出现 关注一下
------
o
------
引用 14 楼 axman 的回复:
再一次说明你确实没有理解什么叫引用。不客气地说以你现在的能力还描述不清楚它们。

------
引用 15 楼 ZangXT 的回复:
“public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
这两个命名常量和字符串常量一样,在常数池中分配空间。 ”
这是明显错误的,常量池不会把类库里的static final的东西都放进去。

------
先顶,再慢慢看

------
话说一定要有争论,帖子才会火,事情才会搞明白
------
要把这些问题搞清楚,得先研读JVM SPEC
光读JVM SPEC还不够,还得了解JVM的实现方法
就这些,至少也得花去一个人几个月甚至几年的时间

我的建议是:大家尽可能去弄懂这些,但没有必要追求100%,
因为即使“不知String s="abc";和String s=new String("abc");的区别”,也无大碍。
这毕竟没到“不知session和request属性的生命周期”这么严重的地步
------
引用 22 楼 yqj2065 的回复:
这里讨论一下:
1、Java虚拟机中没有boolean,我们如何理解Java语言中的boolean,逻辑上或语义上。
2、类库里的static final的东西存放在哪里?类库中的东西和你自己定义类中的东西,语义上没有区别。
3、static final(命名常量)到底如何存放,我还不太清楚,请你补充。

------
1、【Java虚拟机中没有boolean,我们如何理解Java语言中的boolean,逻辑上或语义上。】
我的意思,对于“Java语义的理解”不同于“从JVM的角度去说明”。

例如我说int是32位的,如果从JVM去强调它可能不是这样处理的,没有意义;
例如,声明byte变量并不比声明int是32位节约空间,这有一点意义。

2、
public static final Boolean TRUE = new Boolean(true); 
public static final Boolean FALSE = new Boolean(false); 
这两个命名常量在逻辑上应该有存在空间的,至于它变成了CONSTANT_Integer 还是别的什么我不关心,
请问它在哪里存放?给一个语义上的解释。
------
关于引用:

1、在不同的Java虚拟机中,引用可能是作为指针实现的,其优点是速度快;可能是作为句柄实现的,其优点是容易在内存中调整对象的位置以减少内存碎片。据介绍Sun公司的一些Java虚拟机中,“指向对象的引用”是作为“指向句柄的指针”实现的。这里,引用是一个指向句柄的指针,而句柄本身由两个指针构成。
2、语义上,引用(reference)是引用变量的值、是new表达式的值、是对象的唯一识别号。
3、Each string literal is a reference (§4.3) to an instance (§4.3.1, §12.5) of class String (§4.3.3). JLS。 所以,"abc"是一个引用,是一种精确的说法(虽然我们经常说混用地它是一个String对象)

------

------
Inside the Java Virtual Machine中的说明:
http://www.artima.com/insidejvm/ed2/jvm5.html
------
观看.
------
关于引用的实现,sun的hotspot有这样的介绍如下。不过虚拟机中把引用表示为什么对java编程人员并不重要。
来源:http://java.sun.com/products/hotspot/whitepaper.html
Memory Model
Handleless Objects

In previous versions of the Java virtual machine, such as the Classic VM, indirect handles are used to represent object references. While this makes relocating objects easier during garbage collection, it represents a significant performance bottleneck, because accesses to the instance variables of Java programming language objects require two levels of indirection.

In the Java HotSpot VM, no handles are used by Java code. Object references are implemented as direct pointers. This provides C-speed access to instance variables. When an object is relocated during memory reclamation, the garbage collector is responsible for finding and updating all references to the object in place.
Two-Word Object Headers

The Java HotSpot VM uses a two machine-word object header, as opposed to three words in the Classic VM. Since the average Java object size is small, this has a significant impact on space consumption -- saving approximately eight percent in heap size for typical applications. The first header word contains information such as the identity hash code and GC status information. The second is a reference to the object's class. Only arrays have a third header field, for the array size.
Reflective Data are Represented as Objects

Classes, methods, and other internal reflective data are represented directly as objects on the heap (although those objects may not be directly accessible to Java technology-based programs). This not only simplifies the VM internal object model, but also allows classes to be collected by the same garbage collector used for other Java programming language objects.
Native Thread Support, Including Preemption and Multiprocessing

Per-thread method activation stacks are represented using the host operating system's stack and thread model. Both Java programming language methods and native methods share the same stack, allowing fast calls between the C and Java programming languages. Fully preemptive Java programming language threads are supported using the host operating system's thread scheduling mechanism.

A major advantage of using native OS threads and scheduling is the ability to take advantage of native OS multiprocessing support transparently. Because the Java HotSpot VM is designed to be insensitive to race conditions caused by preemption and/or multiprocessing while executing Java programming language code, the Java programming language threads will automatically take advantage of whatever scheduling and processor allocation policies the native OS provides. 


------
引用 10 楼 meneil 的回复:
如果楼主真的为大家着想就把那个你觉得错的地方改成正确的,这样比到处说别人的不好会好些

------
出发点是好的就行~~
  技术水平不够,看不懂各位的分析,帮忙顶顶帖吧
------
Constants (class variables declared final) are not treated in the same way as non-final class variables. Every type that uses a final class variable gets a copy of the constant value in its own constant pool. As part of the constant pool, final class variables are stored in the method area--just like non-final class variables. But whereas non-final class variables are stored as part of the data for the type that declares them, final class variables are stored as part of the data for any type that uses them. This special treatment of constants is explained in more detail in Chapter 6, "The Java Class File." 


------
引用 36 楼 yqj2065 的回复:
Constants (class variables declared final) are not treated in the same way as non-final class variables.Every type that uses a final class variable gets a copy of the constant value in its own constant pool.As part of the constant pool, final class variables are stored in the method area--just like non-final class variables. But whereas non-final class variables are stored as part of the data fo…

------
关注。
但是并不认为讨论过深有必要。大多数人做的是应用, 如果非要做那么底层, 写汇编好了。

------
非常值得推荐.
------
没怎么看明白


<------------------------------------------->
JAVA免费开源论坛http://www.jsprun.net
------
我也没怎么看懂
菜鸟一只
------
先mark
------

------
也不知道到底谁说的对了~~~
------
我只讲两点:

1 . java只有传值,对象靠引用来调用,对象参数传递的时候传递“引用”的值,用这点解释足够了,概念创造越多初学者越糊涂。 

2. 至于数值存放在哪里?只要记住“所有局部变量存放在栈中,所有对象存堆中”就可以了,具体实例可以据此引申出来。 
  a 局部变量如果是基本类型,则存栈中;如果是对象类型,实际上操作的是一个引用,引用存栈中,对象存堆中。 
  b 对象存堆中的含义就是对象的所有字段存堆中,所以对象的字段不管是基本数据类型和引用一律在堆中,引用指向的对象自然也在堆中。 

------
ding
------
不做评论
------
顶!
------
答:不论贴子的内容质量如何,楼主的这种精神,是很值得顶的.
------
顶···就需要交流················不论人
------
我才明白,不会有错吧?
------
引用 49 楼 jiangnaisong 的回复:
答:不论贴子的内容质量如何,楼主的这种精神,是很值得顶的.

------
才开始自学 什么都不懂慢慢学习

------
i hava no idea about it
------
恩,内容不错,值得学习交流
------
进来学习的
------
已经改成 烂文 了,呵呵!
------
说的好 支持你
------
这些文章说的不都是一个意思么,发那么多篇干嘛
------
"abc"是引用,不太好理解~
------
...
------
ldc "Hello World" ; push string "Hello World" onto stack
ldc 10 ; push the integer 10
ldc 1.54 ; push the single-precision float 1.54


ldc <value>
<value> is an int, a float, or a quoted (i.e. literal) string.
------
好乱好乱啊。。。遇到一些String的问题,就一直看内存的问题,,,看到很多观点,都不知道哪个正确
------
新手 看不懂啊
------

有没有人可以解释一下 引用 变量 和对象之间的关系啊  

有点糊涂了!!
------
学习学习!不是很会
------
引用 65 楼 camelkiss 的回复:

有没有人可以解释一下 引用 变量 和对象之间的关系啊

有点糊涂了!!

------
随便看看,呵呵
------
继续顶
------
引用 68 楼 zidane1983 的回复:
引用 17 楼 axman 的回复:
static final常量是编译期常量,即使程序没有运行它已经在执行码中占用了空间.所以不可能在运行期的常量池中的.
(static final表达式不一定,看是否为常量表达式)

不想再说了.不深入了解JVM的讨论这些问题没有任何意义,不是靠一知半解加想象的.比如JMM这些JVM的基础的基础也没有弄清.

如果说连机械原理也不了解来讨论发动机工作时的压缩比,有什么意义呢?

我虽然不能一个人写出一个JDK,但给我一…

------
路过

有事看不完了

留个记号
------
我还以为这个帖子都沉了呢
------
迷糊学习中
------
mark
------
我都不懂喃~~~~你们好厉害!!
------
mark
------
kankanakna
------
欣赏
------
引用 67 楼 jiangnaisong 的回复:
引用 65 楼 camelkiss 的回复:

有没有人可以解释一下  引用 变量  和对象之间的关系啊

有点糊涂了!!

答:说个比喻吧。虽然所有的比喻都是不精确的。
你本人,是一个丰富多彩的存在。 相当于是“对象”
你的学号(唯一性,每人一个),是一个值,相当于你这个人(“对象”)的“引用”。记住:引用都是具体的值。
有一个抽屉,今天放你的学号(“引用”),明天放他的学号(“引用”),这个存放具体“引用”的抽屉,…

------
进来学习
------
学习学习
------
学习中
------
引用 80 楼 fengzhiquxiang1 的回复:
引用 67 楼 jiangnaisong 的回复:
引用 65 楼 camelkiss 的回复:

有没有人可以解释一下 引用 变量 和对象之间的关系啊

有点糊涂了!!

答:说个比喻吧。虽然所有的比喻都是不精确的。
你本人,是一个丰富多彩的存在。 相当于是“对象”
你的学号(唯一性,每人一个),是一个值,相当于你这个人(“对象”)的“引用”。记住:引用都是具体的值。
有一个抽屉,今天放你的学号(“引用”),明天放他的学号(…

------
学习
------
看看
------
mark

------
mark~
------
dsdsd
------
收藏
------
up
很好~~~
------
此帖还没沉...?
------
哈虎 , 九椒双

------
我来学习啊  
教教我怎么学啊
------
看了之后,收益良多的。
------
回帖是一种美德!
------
学习
------
呵呵·····
------
路过,学习下
------
学习。。。。。收藏了。。。。
桂ICP备07017180号