java中的堆,栈和方法区(转)

2019/04/11 18:14
阅读数 13

来源:https://www.cnblogs.com/iliuyuet/p/5603618.html

https://blog.csdn.net/lin542405822/article/details/80338256 

https://www.cnblogs.com/yangmengdx3/p/4690953.html

关于java中堆栈的存储,先要说一下java的数据类型:

基本类型: 共有8种,即int, short, long, byte, float, double, boolean, char。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出 后,字段值就消失了),出于追求速度的原因,就存在于栈中。栈的存取速度要比堆快每个线程都有自己独立的栈,当方法执行完毕,栈里面的内存空间就会自动被回收,而堆在整个JVM中只有一个(所以堆中的数据可被多个线程共享),堆里面的内存空间由GC来负责回收。

 

包装数据类型:如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创 建,因此比较灵活,但缺点是要占用更多的时间。

注意:String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java 中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单 例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。

 

下面举些例子来说明:

关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤: 

(1)先定义一个名为str的对String类的对象引用变量:String str; 

(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并 将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。 

(3)将str指向对象o的地址。 

 *********************************************************************************************************

为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。 


String str1 = "abc"; 
String str2 = "abc"; 
System.out.println(str1==str2); //true 

注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值(也就是栈中对象的应用相同!!!)。而我们在这里要看的是,str1与str2是否都指向了同一个对象。 
事实上:JVM在栈中创建了两个引用str1和str2,但在堆中只创建了一个对象,而且两个引用都指向了这个对象。 

我们再来更进一步,将以上代码改成: 

String str1 = "abc"; 
String str2 = "abc"; 
str1 = "bcd"; 
System.out.println(str1 + "," + str2); //bcd, abc 
System.out.println(str1==str2); //false 

这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。 

 

我们再接着看以下的代码。 

String str1 = new String("abc"); 
String str2 = "abc"; 
System.out.println(str1==str2); //false 

创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。 

事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然 后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良 影响。 

 

 

我们写代码时的注意点:

(1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向 String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因 此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认 识到这一点对排除程序中难以发现的bug是很有帮助的。

(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是 享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。 

(3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。 

(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。 

 

堆栈中数据的创建和清理

1.  基本数据类型、对象的引用,局部变量都是存放在栈内存中的。

    它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,栈被线程私有。

    基本类型的变量和对象的引用变量都是在函数的 栈内存中分配。

    栈内存中的数据,没有默认初始化值,需要手动设置。
2. new创建的实例化对象及数组,是存放在堆内存中的,当堆内存中的数据不再被栈引用时,就会被gc回收。

 这块区域也是线程共享的。

   堆内存中所有的实体都有内存地址值,并且都有默认的初始值。

 

3.方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。静态变量    +常量+类信息+运行时常量池存在方法区中,实例变量存在堆内存中。
  又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。 
  方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。 

 

 

例:

public   class  AppMain                //运行时, jvm 把appmain的信息都放入方法区    
{    
  public   static   void  main(String[] args)  //main 方法本身放入方法区。    
  {    
    Sample test1 = new  Sample( " 测试1 " );   //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面    
    Sample test2 = new  Sample( " 测试2 " );    
  
    test1.printName();    
    test2.printName();    
  }    
} 
public class Sample //运行时, jvm 把appmain的信息都放入方法区 { /** 范例名称 */   private name; //new Sample实例后, name 引用放入栈区里, name 对象放入堆里 /** 构造方法 */   public Sample(String name)   {      this .name = name;   } /** 输出 */   public void printName() //print方法本身放入 方法区里。   {     System.out.println(name);   } }

下面是一些从其他的地方找的能加深理解的例子(https://www.cnblogs.com/ibelieve618/p/6380328.html

 

例1:

 

main()
  int x=1;
show ()
  int x=2

主函数main()中定义变量int x=1,show()函数中定义变量int x=1。最后show()函数执行完毕。


以上程序执行步骤:

第1步——main()函数是程序入口,JVM先执行,在栈内存中开辟一个空间,存放int类型变量x,同时附值1。
第2步——JVM执行show()函数,在栈内存中又开辟一个新的空间,存放int类型变量x,同时附值2。
     此时main空间与show空间并存,同时运行,互不影响。
第3步——show()执行完毕,变量x立即释放,空间消失。但是main()函数空间仍存在,main中的变量x仍然存在,不受影响。

 

例2

main()
  int[] x=new int[3];
  x[0]=20
  x=null;

以上步骤执行步骤
第1、2步——与示例2完全一样,略。

第3步——执行x=null;
  null表示空值,即x的引用数组内存地址0x0045被删除了,则不再指向堆中的数组。此时,堆中的数组不再被x使用了,即被视为垃圾,JVM会启动垃圾回收机制,不定时自动删除。

例3:

Car c=new Car;
c.num=5;
Car c1=c;
c1.color="green";
c.run();

Car c1=c,这句话相当于将对象复制一份出来,两个对象的内存地址值一样。所以指向同一个实体,对c1的属性修改,相当于c的属性也改了。

。。。

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部