java 栈内存中局部变量 栈没有地址吗?比如0x0034什么的

java中有没有取变量的地址的运算符呢?_百度知道
java中有没有取变量的地址的运算符呢?
提问者采纳
Integer.toHexString(super.hashCode())就是调用Object的hashCode可以得到对象的地址。
提问者评价
其他类似问题
默认的情况是只有当两个比较对象(这里是a.com/question/,比如你有个类叫做A类,如果想比较a和b是不是一个对象,这个是object的属性.这个值是干什么用的呢,有两个实例化对象A a = new A().com/question/.那么什么时候equals方法是返回true呢,java对象其实都是jvm内存中对象的一个引用)!我也是在别处看来的.html" target="_blank">http,b)是一个对象的时候?这个值主要是用来标识对象的唯一性的?就需要调用equals方法,希望能帮到你<a href="http:// A b = new A().baidu,而所有对象都继承自object类.而这个hashcode存储的正是当前变量引用的对象的地址?怎么比;这个时候,所谓一个对象就是引用的一个地址空间内的对象(你也应该知道.就是说银河一个java对象都有这个值://zhidaojava对象都有一个属性是hashcode,equals方法默认的就是通过hashcode这个值来比较.baidu
运算符的相关知识
按默认排序
其他1条回答
java没有指针的概念,没有取地址操作。
为您推荐:
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁13703人阅读
1.java是如何管理内存的
java的内存管理就是对象的分配和释放问题。(其中包括两部分)
分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(Heap)中分配空间。
释放:对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作。但同时,它也加重了JVM的工作。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋&#20540;等,GC都需要进行监控。
2.什么叫java的内存泄露
在java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连(也就是说仍存在该内存对象的引用);其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
3.JVM的内存区域组成
java把内存分两种:一种是栈内存,另一种是堆内存
(1)在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配;
(2)堆内存用来存放由new创建的对象和数组以及对象的实例变量。在函数(代码块)中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理
堆和栈的优缺点
堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。
缺点就是要在运行时动态分配内存,存取速度较慢;栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。
另外,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
4.java中数据在内存中是如何存储的
a)基本数据类型
java的基本数据类型共有8种,即int,short,long,byte,float,double,boolean,char(注意,并没有String的基本类型&)。这种类型的定义是通过诸如int a = 3;long b = 255L;的形式来定义的。如int a = 3;这里的a是一个指向int类型的引用,指向3这个字面&#20540;。这些字面&#20540;的数据,由于大小可知,生存期可知(这些字面&#20540;定义在某个程序块里面,程序块退出后,字段&#20540;就消失了),出于追求速度的原因,就存在于栈中。
另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。比如:
我们同时定义:
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面&#20540;为3的地址,没找到,就开辟一个存放3这个字面&#20540;的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b这个引用变量后,由于在栈中已经有3这个字面&#20540;,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
定义完a与b的&#20540;后,再令a = 4;那么,b不会等于4,还是等于3。在编译器内部,遇到时,它就会重新搜索栈中是否有4的字面&#20540;,如果没有,重新开辟地址存放4的&#20540;;如果已经有了,则直接将a指向这个地址。因此a&#20540;的改变不会影响到b的&#20540;。
在java中,创建一个对象包括对象的声明和实例化两步,下面用一个例题来说明对象的内存模型。假设有类Rectangle定义如下:
public&class&Rectangle {double&width;double&height;public&Rectangle(&double&w,&double&h){w =&width;h =&height;}}
(1)声明对象时的内存模型
用Rectangle rect;声明一个对象rect时,将在栈内存为对象的引用变量rect分配内存空间,但Rectangle的&#20540;为空,称rect是一个空对象。空对象不能使用,因为它还没有引用任何”实体”。
(2)对象实例化时的内存模型
当执行rect=new Rectangle(3,5);时,会做两件事:在堆内存中为类的成员变量width,height分配内存,并将其初始化为各数据类型的默认&#20540;;接着进行显式初始化(类定义时的初始化&#20540;);最后调用构造方法,为成员变量赋&#20540;。返回堆内存中对象的引用(相当于首地址)给引用变量rect,以后就可以通过rect来引用堆内存中的对象了。
c)创建多个不同的对象实例
一个类通过使用new运算符可以创建多个不同的对象实例,这些对象实例将在堆中被分配不同的内存空间,改变其中一个对象的状态不会影响其他对象的状态。例如:
Rectangle&r1=&new&Rectangle(3,5);Rectangle&r2=&new&Rectangle(4,6);
此时,将在堆内存中分别为两个对象的成员变量&width&、&height&分配内存空间,两个对象在堆内存中占据的空间是互不相同的。如果有:
Rectangle&r1=new&Rectangle(3,5);Rectangle&r2=r1;
则在堆内存中只创建了一个对象实例,在栈内存中创建了两个对象引用,两个对象引用同时指向一个对象实例。
基本类型都有对应的包装类:如int对应Integer类,double对应Double类等,基本类型的定义都是直接在栈中,如果用包装类来创建对象,就和普通对象一样了。例如:int i=0;i直接存储在栈中。Integer i(i此时是对象)= new Integer(5);这样,i对象数据存储在堆中,i的引用存储在栈中,通过栈中的引用来操作对象。
String是一个特殊的包装类数据。可以用以下两种方式创建:String str =&new&String(“abc”);String str = “abc”;
第一种创建方式,和普通对象的的创建过程一样;
第二种创建方式,java内部将此语句转化为以下几个步骤:
(1)先定义一个名为str的对String类的对象引用变量:String str;
(2)在栈中查找有没有存放&#20540;为”abc”的地址,如果没有,则开辟一个存放字面&#20540;为”abc”
地址,接着创建一个新的String类的对象o,并将o的字符串&#20540;指向这个地址,而且在栈
这个地址旁边记下这个引用的对象o。如果已经有了&#20540;为”abc”的地址,则查找对象o,并
回o的地址。
(3)将str指向对象o的地址。
&#20540;得注意的是,一般String类中字符串&#20540;都是直接存&#20540;的。但像String str = “abc”;这种
合下,其字符串&#20540;却是保存了一个指向存在栈中数据的引用。
为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。
String str1=&abc&;String str2=&abc&;System.out.println(s1==s2);//true
注意,这里并不用&str1.equals(str2);的方式,因为这将比较两个字符串的&#20540;是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真&#20540;。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
我们再接着看以下的代码。
String str1=&new&String(&abc&);String str2=&&abc&;System.out.println(str1==str2);//false
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。
以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存&#20540;的,即使与栈中的数据相同,也不会与栈中的数据共享。
当定义一个数组,int x[];或int[] x;时,在栈内存中创建一个数组引用,通过该引用(即数组名)来引用数组。x=new int[3];将在堆内存中分配3个保存&int型数据的空间,堆内存的首地址放到栈内存中,每个数组元素被初始化为0。
g)静态变量
用static的修饰的变量和方法,实际上是指定了这些变量和方法在内存中的”固定位置”-static storage,可以理解为所有实例对象共有的内存空间。static变量有点类&#20284;于C中的全局变量的概念;静态表示的是内存的共享,就是它的每一个实例都指向同一个内存地址。把static拿来,就是告诉JVM它是静态的,它的引用(含间接引用)都是指向同一个位置,在那个地方,你把它改了,它就不会变成原样,你把它清理了,它就不会回来了。
那静态变量与方法是在什么时候初始化的呢?对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。
我们常可看到类&#20284;以下的例子来说明这个问题:
class&Student{static&int&numberOfStudents&=0;Student(){numberOfStudents&&#43;&#43;;}}
每一次创建一个新的Student实例时,成员numberOfStudents都会不断的递增,并且所有的Student实例都访问同一个numberOfStudents变量,实际上intnumberOfStudents变量在内存中只存储在一个位置上。
5.java的内存管理实例
Java程序的多个部分(方法,变量,对象)驻留在内存中以下两个位置:即堆和栈,现在我们只关心三类事物:实例变量,局部变量和对象:
实例变量和对象驻留在堆上
局部变量驻留在栈上
让我们查看一个&java&程序,看看他的各部分如何创建并且映射到栈和堆中:
public&class&Dog {Collar c;String&name;//1.main()方法位于栈上public&static&void&main(String[] args) {//2.在栈上创建引用变量d,但Dog对象尚未存在Dog d;//3.创建新的Dog对象,并将其赋予d引用变量d =&new&Dog();//4.将引用变量的一个副本传递给go()方法d.go(d);}//5.将go()方法置于栈上,并将dog参数作为局部变量void&go(Dog dog){//6.在堆上创建新的Collar对象,并将其赋予Dog的实例变量c =&new&Collar();}//7.将setName()添加到栈上,并将dogName参数作为其局部变量void&setName(String dogName){//8.name的实例对象也引用String对象name&=dogName;}//9.程序执行完成后,setName()将会完成并从栈中清除,此时,局部变量dogName也会消失,尽管它所引用的String仍在堆上}
6.&垃圾回收机制
问题一:什么叫垃圾回收机制?
垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用,以免造成内存泄露。
问题二:java的垃圾回收有什么特点?
jAVA语言不允许程序员直接控制内存空间的使用。内存空间的分配和回收都是由JRE负责在后台自动进行的,尤其是无用内存空间的回收操作(garbagecollection,也称垃圾回收),只能由运行环境提供的一个超级线程进行监测和控制。
问题三:垃圾回收器什么时候会运行?
一般是在CPU空闲或空间不足时自动进行垃圾回收,而程序员无法精确控制垃圾回收的时机和顺序等。、
问题四:什么样的对象符合垃圾回收条件?
当没有任何获得线程能访问一个对象时,该对象就符合垃圾回收条件。
问题五:垃圾回收器是怎样工作的?
垃圾回收器如发现一个对象不能被任何活线程访问时,他将认为该对象符合删除条件,就将其加入回收队列,但不是立即销毁对象,何时销毁并释放内存是无法预知的。垃圾回收不能强制执行,然而java提供了一些方法(如:System.gc()方法),允许你请求JVM执行垃圾回收,而不是要求,虚拟机会尽其所能满足请求,但是不能保证JVM从内存中删除所有不用的对象。
问题六:一个java程序能够耗尽内存吗?
可以。垃圾收集系统尝试在对象不被使用时把他们从内存中删除。然而,如果保持太多活的对象,系统则可能会耗尽内存。垃圾回收器不能保证有足够的内存,只能保证可用内存尽可能的得到高效的管理。
问题七:如何显示的使对象符合垃圾回收条件?
(1)空引用:当对象没有对他可到达引用时,他就符合垃圾回收的条件。也就是说如果没有对他的引用,删除对象的引用就可以达到目的,因此我们可以把引用变量设置为null,来符合垃圾回收的条件。
StringBuffer sb =&new&StringBuffer(&hello&);System.out.println(sb);sb=&null;
(2)重新为引用变量赋&#20540;:可以通过设置引用变量引用另一个对象来解除该引用变量与一个对象间的引用关系。
StringBuffer sb1 =&new&StringBuffer(“hello”);
StringBuffer sb2 =&new&StringBuffer(“goodbye”);
System.out.println(sb1);
sb1=sb2;//此时”hello”符合回收条件
(3)方法内创建的对象:所创建的局部变量仅在该方法的作用期间内存在。一旦该方法返回,在这个方法内创建的对象就符合垃圾收集条件。有一种明显的例外情况,就是方法的返回对象。
public&static&void&main(String[] args) {Date d = getDate();System.out.println(&d=&&#43;d);}private&static&Date getDate() {Date d2 =&new&Date();StringBuffer now =&new&StringBuffer(d2.toString());System.out.println(now);return&d2;}
(4)隔离引用:这种情况中,被回收的对象仍具有引用,这种情况称作隔离岛。若存在这两个实例,他们互相引用,并且这两个对象的所有其他引用都删除,其他任何线程无法访问这两个对象中的任意一个。也可以符合垃圾回收条件。
public&class&Island {Island&i;public&static&void&main(String[] args) {Island i2 =&new&Island();Island i3 =&new&Island();Island i4 =&new&Island();i2.&i&=i3;i3.&i&=i4;i4.&i&=i2;i2=&null;i3=&null;i4=&null;}}
问题八:垃圾收集前进行清理——finalize()方法
java提供了一种机制,使你能够在对象刚要被垃圾回收之前运行一些代码。这段代码位于名为finalize()的方法内,所有类从Object类继承这个方法。由于不能保证垃圾回收器会删除某个对象。因此放在finalize()中的代码无法保证运行。因此建议不要重写finalize();
7.final问题
final使得被修饰的变量”不变”,但是由于对象型变量的本质是”引用”,使得”不变”也有了两种含义:引用本身的不变和引用指向的对象不变。
引用本身的不变:
final&StringBuffer a=&new&StringBuffer(&immutable&);final&StringBuffer b=&new&StringBuffer(&not immutable&);a=b;//编译期错误final StringBuffer a=new StringBuffer(&immutable&);final StringBuffer b=new StringBuffer(&not immutable&);
a=b;//编译期错误
引用指向的对象不变:
final&StringBuffer a=&new&StringBuffer(&immutable&);a.append(&broken!&);//编译通过final StringBuffer a=new StringBuffer(&immutable&);a.append(&broken!&);//编译通过
可见,final只对引用的”&#20540;”(也即它所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。这很类&#20284;==操作符:==操作符只负责引用的”&#20540;”相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的。在举一个例子:
public&class&Name {private&String&firstname;private&String&lastname;public&String getFirstname() {return&firstname;}public&void&setFirstname(String firstname) {this.firstname&= firstname;}public&String getLastname() {return&lastname;}public&void&setLastname(String lastname) {this.lastname&= lastname;}}&public class Name {private String firstname;private String lastname;public String getFirstname() {return firstname;}public void setFirstname(String firstname) {this.firstname = firstname;}public String getLastname() {return lastname;}public void setLastname(String lastname) {this.lastname = lastname;}}
编写测试方法:
public&static&void&main(String[] args) {final&Name name =&new&Name();name.setFirstname(&JIM&);name.setLastname(&Green&);System.out.println(name.getFirstname()&#43;&& &&&#43;name.getLastname());}public static void main(String[] args) {final Name name = new Name();name.setFirstname(&JIM&);name.setLastname(&Green&);System.out.println(name.getFirstname()&#43;& &&#43;name.getLastname());}
理解final问题有很重要的含义。许多程序漏洞都基于此—-final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它”永远不变”。其实那是徒劳的.final还有一个&#20540;得注意的地方,
先看以下示例程序:
class&Something {final&int&i&;public&void&doSomething() {System.&out&.println(&&i = &&&#43;&i&);}}class Something {final int i;public void doSomething() {System.out.println(&i = & &#43; i);}}
对于类变量,java虚拟机会自动进行初始化。如果给出了初始&#20540;,则初始化为该初始&#20540;。如果没有给出,则把它初始化为该类型变量的默认初始&#20540;。但是对于用final修饰的类变量,虚拟机不会为其赋予初&#20540;,必须在constructor(构造器)结束之前被赋予一个明确的&#20540;。可以修改为”final int i = 0;”。
8.如何把程序写得更健壮
(1)尽早释放无用对象的引用。
好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。对于仍然有指针指向的实例,jvm就不会回收该资源,因为垃圾回收会将&#20540;为null的对象作为垃圾,提高GC回收机制效率;
(2)定义字符串应该尽量使用String str=”hello”;的形式,避免使用String str = new String(“hello”);的形式。因为要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始&#20540;,应当这样做:
public&class&Demo {private&String s;public&Demo() {s =&&Initial Value&;}}&public class Demo {private String s;...public Demo {s = &Initial Value&;}...}而非s&=&&new&&String(&Initial&Value&);s = new String(&Initial Value&);
后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
(3)我们的程序里不可避免大量使用字符串处理,避免使用String,应大量使用StringBuffer,因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象,请看下列代码;
String&s&=&&Hello&;s&=&s&&#43;&&&world!&;String s = &Hello&;s = s &#43; & world!&;
在这段代码中,s原先指向一个String对象,内容是”Hello”,然后我们对s进行了&#43;操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个String对象,内容为”Hello world!”,原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
(4)尽量少用静态变量,因为静态变量是全局的,GC不会回收的;
(5)尽量避免在类的构造函数里创建、初始化大量的对象,防止在调用其自身类的构造器时造成不必要的内存资源浪费,尤其是大对象,JVM会突然需要大量内存,这时必然会触发GC优化系统内存环境;显示的声明数组空间,而且申请数量还极大。
以下是初始化不同类型的对象需要消耗的时间:
标准化时间
本地赋&#20540;
实例赋&#20540;
this.i = n
New Object()
New int[10]
从表中可以看出,新建一个对象需要980个单位的时间,是本地赋&#20540;时间的980倍,是方法调用时间的166倍,而新建一个数组所花费的时间就更多了。
(6)尽量在合适的场景下使用对象池技术以提高系统性能,缩减缩减开销,但是要注意对象池的尺寸不宜过大,及时清除无效对象释放内存资源,综合考虑应用运行环境的内存资源限制,避免过高估计运行环境所提供内存资源的数量。
(7)大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
(8)不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用hashtable,vector创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃。
(9)一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成Out Of Memory Error的状况,这时就大概要计算一下数据量的最大&#20540;是多少,并且设定所需最小及最大的内存空间&#20540;。
(10)尽量少用finalize函数,因为finalize()会加大GC的工作量,而GC相当于耗费系统的计算能力。
(11)不要过滥使用哈希表,有一定开发经验的开发人员经常会使用hash表(hash表在JDK中的一个实现就是HashMap)来缓存一些数据,从而提高系统的运行速度。比如使用HashMap缓存一些物料信息、人员信息等基础资料,这在提高系统速度的同时也加大了系统的内存占用,特别是当缓存的资料比较多的时候。其实我们可以使用操作系统中的缓存的概念来解决这个问题,也就是给被缓存的分配一个一定大小的缓存容器,按照一定的算法淘汰不需要继续缓存的对象,这样一方面会因为进行了对象缓存而提高了系统的运行效率,同时由于缓存容器不是无限制扩大,从而也减少了系统的内存占用。现在有很多开源的缓存实现项目,比如ehcache、oscache等,这些项目都实现了FIFO&、MRU等常见的缓存算法。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:391251次
积分:5569
积分:5569
排名:第1971名
原创:143篇
转载:160篇
评论:82条
(2)(8)(6)(8)(3)(6)(10)(6)(11)(10)(5)(1)(2)(1)(2)(6)(13)(1)(1)(6)(7)(8)(10)(24)(17)(10)(6)(9)(38)(28)(6)(2)(3)(7)(20)从“关于Java堆与栈的思考”一帖看错误信息的传播 - 走火入魔的Java初学者 - ITeye技术网站
博客分类:
我对转贴的信息一直有敌意,原因如下:首先,除了制造更多的信息垃圾,转贴不会带来新的价值,想收藏的话一个链接足矣;其次,将错误信息以讹传讹,混淆视听。不妨选一个典型的例子说明一二。
相信《关于Java堆与栈的思考》这个帖子大家并不陌生,转载量极大。但内容如何呢?我就试着分析一下。说明:以下内容,黑色字体为引用别人的帖子,红色字体是我的分析,蓝色字体是引用相关文献。
1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
与C++不同,对于堆来说是有道理的。但C++的栈也是由编译器负责安排和布局的,这和java没什么区别。其实,java里提到的堆和栈应该是逻辑层面上的,是jvm划分的,并不同于C++的实际运行时内存,因此根本不是一个层面上的东西,最好不要进行比较。2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
在逻辑上看,可以认为栈比堆快,但是请不要和寄存器比。上面已经提到,java所谓的堆和栈是逻辑上的,《The Java Virtual Machine Specification》中,将运行时数据区分为pc寄存器、java虚拟机栈、堆、方法区、运行时常量池、本地方法栈共6个部分,而CPU中的寄存器是物理实体。
比如java执行一个加法操作,逻辑上看是读取栈顶的两个数据,执行加法,将结果写回栈顶,但实际的代码经过jvm jit编译之后很可能就是在寄存器中执行加法。因此逻辑意义上的比较根本没有什么价值。
"栈数据可以共享",这个说法是该帖子"创造性"的提出来的。翻遍所有的java 资料,根本找不到这样的说法(现在看来,也不全是,国内已经有书籍将这个帖子完全照搬了,抄袭可耻!貌似有本《java程序员面试宝典》,看过的都知道,呵呵)。 3. Java中的数据类型有两种。
一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。
这里混淆了一个问题。首先要知道什么是字面值,所谓的字面值是形如1,3,2.0,2.0f,'c',"Hello"这样的形式出现的常量。基本类型变量里存的是什么?值。存到变量中就不存在"字面值"一说了!"如int a = 3; 这里的a是一个指向int类型的引用",这个例子就太不恰当了,我们要注意的是,int a = 3;我们得到的是一个空间,里面存的是3这个数。这里又"int类型的引用"云云,岂不缪哉?
这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。
基本类型的变量存在栈里或者堆里不是由"大小可知,生存期可知"就能确定了。关键是上下文。
void func(){
int a = 3;
这自然是存在栈里的。
class Test{int a = 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的值。
这段就特别离谱了。首先我们要知道基本类型的变量里存的是什么。参考《The Java Language Specification》:Primitive values do not share state with other primitive values. A variable whose type is a primitive type always holds a primitive value of that same type.也就是说基本类型的变量中存放的就是该基本类型的值。 
下面我们再看那栈中变量是如何存储的。Java是将所有的局部变量按数组的方式编号存储的。其中,一个int,float占该数组中的1项,一个long,double变量占数组的两项,引用变量占1项。其余byte、char、short等得不到jvm承认的就按int处理了。
  看一个实际的例子:
public class Main {
public static void main(String[] args){
int i = 3;
int j = 3;
int p = 10;
int q = 10;
Object o = new Object();
char c = 'c';
double d = 1;
int m = 10;
javap -c Main,反汇编处理:
public static void main(java.lang.String[]);
#2; //class java/lang/Object
invokespecial
#1; //Method java/lang/Object."":()V
  其实反汇编之后可以看出,程序中的变量名已经不存在了,代码中是按变量在局部变量数组中的索引来处理的(学过编译的同学应该很容易明白)。这里main函数的栈帧(Frame)里局部变量的内存布局是:
字节码中的istore后的参数指定存储的目标位置索引。   
容易看出,即使两个变量存储的数据是相同的,它们也是各自存储自己的变量值,互不干涉。比如对p和q的操作是:
10  //将常数10放到栈顶
istore_3   //将栈定的10存入栈变量数组的第三个位置(即p)
10  //同上,同样存储的是10
   当然还有最后的m,也是存储的10。并不是"由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。"
   不过必须指出的是,的确存在数据共享的情形。当然,所谓的共享是指编译器在生成class文件时的数据共享。比如下面的例子:
public class Main {
public static void main(String[] args){
int b1 = 64;
int b2 = 64;
int s1 = 128;
int s2 = 128;
int i1 = 65536;
int i2 = 65536;
double d1 = 15;
double d2 = 15;
public static void main(java.lang.String[]);
#2; //int 65536
#2; //int 65536
#3; //double 15.0d
#3; //double 15.0d
  b1=64&128,我们用1个字节就可以存储这个数了,所以直接用bipush指令+操作数64,该指令一共占2个字节。64&s1=128&65536,常数128两个字节可以表示,因此这里生成的指令是sipush+操作数128,指令占3个字节。而到了65536的时候是如何处理的呢?编译器将它放到了常量池表里,这样编译器生成的class文件中,读取65536这个常数时,只需要1条读取指令和1个1字节的索引,共2个字节。如果直接存这个数的话,我们需要的是加载指令+四个字节操作数。当这个数多次出现时,通过索引进行操作的方式就可以较好的节省操作数所占的字节数(空间)。这样最大的好处就是能有效的减少class文件的大小。
  同理,当处理double类型数据的时候,常数15.0d也是放入常量池里的,这样读取常数进行赋值时,读取指令只用了3个字节(ldc2_w
#3;操作符1个字节,操作数2个字节,如果直接将15.0这个double常数放到指令中的话,单操作数就占去8个字节)。意义上面已经说明。
  注意:不要把我说的共享和原贴所谓的共享混起来。我说的共享是class文件中字面量常数的共享,而变量是独立的、互不影响的,重复一遍,就是double d1 = 15.0;double d2 = 15.0,d1和d2都存的都是15.0,互不影响,但用来初始化它们的参数在class文件中只保存了一份。
4. String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。
  对于Integer i = 3;编译器并不是进行了Integer i = new Integer(3)这样的转换,而是这样处理:
Integer i = Integer.valueOf(3);
  为什么要这样处理呢?因为Java的设计者们有以下考虑(《The Java Language Specification》):
Ideally, boxing a given primitive value p, would always yield an identical reference.[理想的情况下,将一个基本类型值p装箱,总是得到一个同样的引用] In practice, this may not be feasible using existing implementation techniques. The rules above are a pragmatic compromise. The final clause above requires that certain common values always be boxed into indistinguishable objects. The implementation may cache these, lazily or eagerly.
  因技术手段所限,于是有了这样的规定:
If the value p being boxed is true, false, a byte, a char in the range \u0000 to \u007f, or an int or short number between -128 and 127, then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.
  也就有了这样的现象:
Integer i = 3;
Integer j = 3;
System.out.println(i==j);
  打印true。
Integer i = 128;
Integer j = 128;
System.out.println(i==j);
  打印false。
至于实现,有兴趣的可以参考Integer 类中public static Integer valueOf(int i)方法。
关于这一点,有些人也给出了好玩的结论,比如说我上面的这个例子,他们说当变量在-128~127时,比较的是对象的值,而此范围之外是比较的是对象的地址。麻烦不?
5. 关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:
(1)先定义一个名为str的对String类的对象引用变量:String str;
(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。
(3)将str指向对象o的地址。
值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!
  这段讲解问题是比较严重的。String具体是如何处理的呢?前面通过大于65535的int值和一般的double值已经说明了class文件中数据共享的原理,String的处理也是一样的。javac对.java文件进行处理,最后生成Class文件的字节码表示时,会同时生成一个常量池结构。在将数据放入常量池时,会对重复的数据进行过滤(其实是一个Map),保证值保存一份。这一点可以参考OpenJDK中javac的实现,常量池的代码在openjdk\langtools\src\share\classes\com\sun\tools\javac\jvm\Pool.java中,看一下put方法的实现就都清楚了。
  我们可以通过分析class文件证明这些常量的唯一性,例如:
public class Main {
public static void main(String[] args){
double d1 = 15;
double d2 = 15;
String s1 = "Hello World!";
String s2 = "Hello World!";
public static void main(java.lang.String[]);
#2; //double 15.0d
#2; //double 15.0d
#4; //String Hello World!
#4; //String Hello World!
常数15.0在常量池的第二项,而常量String "Hello World!"在常量池的第4项。可以通过javap -verbose 类名,得到常量池的信息。
需要注意的是class文件中的常量池是静态信息,类加载的时候jvm会根据这个常量池的信息在内存中真正建立起常量池结构。
那常量池中的String对象"Hello World!"是何时创建的呢?我们再来看《Java Virtual Machine Specification 》:
Loading of a class or interface that contains a String literal may create a new String object
to represent that literal. This may not occur if the a String object has already been created to represent a previous occurrence of that literal, or if the String.intern method has been invoked on a String object representing the same string as the literal.
To derive a string literal, the Java virtual machine examines the sequence of characters given by the CONSTANT_String_info structure.
If the method String.intern has previously been called on an instance of class String containing a sequence of Unicode characters identical to that given by the CONSTANT_String_info structure, then the result of string literal derivation is a reference to that same instance of class String.
Otherwise, a new instance of class String is created containing the sequence of Unicode characters given by the CONSTANT_String_ that class instance is the result of string literal derivation. Finally, the intern method of the new String instance is invoked.
通过上面的说明,我们知道类加载之后,常量池中已经创建好了字符串常量对象。
在我们的例子中就是,加载Main类的时候,创建了"Hello World!"所表示的字符串对象。因此,执行到String s1 = "Hello World!"; String s2 = "Hello World!";不过把通过#2解析出的字符串常量对象的引用赋给s1和s2。
综上可知,字符串字面中的常量数据是保存在常量池中的。相同的内容也没有帖子中所谓的栈查找过程,当然就更没有所谓的"栈地址旁边记录引用" 之类的东东了。
原帖中又弄了一大堆例子试图说明"栈数据共享"这个论断,这里就不一一引用了。
还有一点需要交代一下,常量池在内存中的Perm区,这里一般是不会被垃圾回收器打扰的。所以,不要认为对常量字符串的引用不存在了之后它就会被回收了。
6. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。7. 结论与建议:
(1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。
这里总结的不错。说到这简单提一下,其实很多书上经常将对象和引用混在一起。比如说Object obj = new Object();说obj是个对象云云。我感觉这是个很坏的习惯。按照《The Java Language Specification》上的说明:
There are two kinds of types in the Java programming language: primitive types
and reference types . There is also a special null type, the type of the expression null, which has no name.
在初学时就自然的把变量分为两种(基本类型变量和引用类型变量)是再好不过的了。我们也不用费力的去分析==和equals()什么关系。因为很自然,==就是比较变量的"值",基本类型变量存的就是数据值,数据相等就可以确定"=="为 true,而引用类型存的是可以找到对象的信息(一般的jvm实现中都将引用作为一个指针,起码SUN和IBM的虚拟机都是这么处理的,可以参考相关资料,这种意义下就可以认为引用保存的是地址值),如果两个引用变量的"值"能让我们找到同一个对象,那它们的"=="也为true。
(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。String str = new String("abc");千万不要这样写代码!
(3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。
(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。
虽然分析基本错误,但原帖中的结论是比较有价值的。
我这里只是以《关于java栈与堆的思考》为例说明一个现象,就是错误的知识因转载和共享也会成为大多数人认定的真理。类似的还有很多很多这样形成的"真理",比如:
《Java关键字final、static使用总结》中有一句话,注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。
想不明白这个因为所以怎么推出来的,本来不相关的两个概念。
有一个帖子《如何优化JAVA程序开发,提高JAVA性能》,对性能分析给出了很多好的建议,但使用String类型举例子,结果带来了很多错误的证据。
还有一个耳熟能详的说法,"如果自己不定义构造方法,编译器自动提供一个public的缺省构造方法",谁说一定是public的呢?
再比如,"一个引用变量不用了之后,使用obj =是个好习惯",了解GC的话,这并不是什么好习惯。可以参考/learning/javaoneonline/2007/pdf/TS-2906.pdf。
&& 刚发完就看到25楼关于默认构造函数的帖子了,呵呵,这里确实是一个误区。默认的类的构造函数的可见性其实是和类本身的可见性相同的。
&& 哈哈,这个题目用来做SCJP看来是个不错的题目哦,十有八九答错!
关于“栈比堆快",可以参考《Practical Java》,里面有具体的说明。
关于“父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。”,参考我的另一个帖子:
/admin/blogs/461227
与C++不同,对于堆来说是有道理的。但C++的栈也是由编译器负责安排和布局的,这和java没什么区别。其实,java里提到的堆和栈应该是逻辑层面上的,是jvm划分的,并不同于C++的实际运行时内存,因此根本不是一个层面上的东西,最好不要进行比较。这个理解是偏差的,堆和栈本身就是逻辑概念,对于编译语言C甚至汇编也好,它们所用到的栈也只是由寄存器实现的。虚拟机的一个方面就是模拟硬件机器,从语言角度来说,两者的堆栈是一致的。&& 引用在逻辑上看,可以认为栈比堆快 这个概念是不存在的,堆和栈的存储目的是不同的,两者本身就不存在比的概念,如果只是比所谓的速度,一个很深的栈访问是肯定比一个很小的堆访问要慢的。&& 引用这里总结的不错。说到这简单提一下,其实很多书上经常将对象和引用混在一起。比如说Object obj = new Object();说obj是个对象云云。我感觉这是个很坏的习惯。按照《The Java Language Specification》上的说明:呵呵,这个说法有些绝对了。 我们可以说obj是一个一个Object类型对象的引用,同样也可以说obj是Object的一个实例,其实这只是一个约定俗成的表达,不必于太过抠字眼的&& 引用《Java关键字final、static使用总结》中有一句话,注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。&&&&&&& 想不明白这个因为所以怎么推出来的,本来不相关的两个概念。 这个说法是没错的,楼主没道理批判的说。&&& 引用还有一个耳熟能详的说法,"如果自己不定义构造方法,编译器自动提供一个public的缺省构造方法",谁说一定是public的呢?这个有错么?倒是愿闻其详
shenjianwangyi 写道
现在就给你一个理由 你说是 谁说一定是public的呢? 你来告诉我 为什么不一定是public的
/admin/blogs/421510
刚看了楼主/admin/blogs/421510这篇博文了,我刚刚测试了一下。的确默认构造器不一定是public的。
哦,我的测试环境是JDK 6.0
测试代码很简单:
public class Main {
* @param args
public static void main(String[] args) throws Exception {
Class&?& clazz = Test.
Constructor&?&[] constructors = clazz.getDeclaredConstructors();
System.out.println(constructors.length);
for (Constructor&?& constructor : constructors) {
System.out.println(constructor);
class Test{
很佩服楼主啊!请问下《The Java Virtual Machine Specification》中文名是叫《深入java虚拟机》吗?能不能推荐下关于java虚拟机的好书?很有兴趣深入学习一下
这不是一本书。
《The Java Virtual Machine Specification》没有中文版,目前最新的是第三版。
《深入java虚拟机》是《Inside the Java Virtual Machine》,有第二版,有中文版。
关于虚拟机的书不多,这两本是权威。
关于虚拟机的更多资料可以看SUN的相关文档和JavaOne上一些讲座。
现在就给你一个理由 你说是 谁说一定是public的呢? 你来告诉我 为什么不一定是public的
/admin/blogs/421510
以讹传讹, 害死人啊... 堆栈的这篇烂文被批N次了shenjianwangyi 写道楼主 不要误导别人好吗您好,可以给出我“误导”的理由吗?现在就给你一个理由 你说是 谁说一定是public的呢? 你来告诉我 为什么不一定是public的
Type mismatch: cannot convert from int to Integer这个就是执行下面程序的结果Integer i = 128;
Integer j = 128;
System.out.println(i==j);其他的我就不说了,你自己认真检讨去吧!楼主就是那种外行误导内行的那种人,我很憎恨,因为就是这种人,使很多真正的人才被埋没被压抑。。。很多外行的人很容易就被这种人给蒙蔽了。。。真正的人才被踩死也只能怨自己命不好了。。。呜呼哀哉。。。这种人一旦握住权利之刀,对真正的人才是相当危险的。而他们在错杀人才的时候,可以说是浑然不知。。。你用的JDK的版本有问题吧,或者你的设置的版本是兼容1.4吧。还有,楼主说得很好,不要在JSP里面写JAVA代码,测也不好测,为什么不写一个类,写一个main方法呢?
因为我用的是jsp!你应该对我说,jsp是不允许这样的!jsp必须这样:
Integer i = new Integer(128);
Integer j = new Integer(128);
System.out.println(i==j);
我给您回站内信了。请检查自己的设置,建议JSP中少写java代码。
mismatch: cannot convert from int to Integer dugu666 写道大哥请解释一下,我用的是jdk1.6
不知道是不是你设置的版本跟你认为的版本不一样。
自己搜一下自动装箱和拆箱的知识。
因为我用的是jsp!你应该对我说,jsp是不允许这样的!jsp必须这样:
Integer i = new Integer(128);
Integer j = new Integer(128);
System.out.println(i==j);
大哥请解释一下,我用的是jdk1.6
不知道是不是你设置的版本跟你认为的版本不一样。
自己搜一下自动装箱和拆箱的知识。
& 上一页 1
浏览: 50695 次
来自: 北京
我在这里补充几点:
1)“还有一点需要交代一下,常量池在内存中 ...
第四题,4.Search for the One因为现在的jd ...
感谢博主的推荐,现在从事Android开发,一直觉得自己的Ja ...
我是来学习的~~

我要回帖

更多关于 配置java环境变量 的文章

 

随机推荐