`
中南大宝
  • 浏览: 34523 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

Java内存之"栈"与"堆"

阅读更多

        昨天中午,发了一篇equals和==区别的博文,晚上再看时有几位大牛指出了其中的一些错误,很感谢他们的留言,一句简简单单的留言给了我对这些错误知识点改正的机会。或许这就是从事互联网行业所提倡的互帮互助的精神吧,因为有分享,有交流,互联网才会发展的如此迅猛。大牛提的一个观点很好,好的东西可以拿出来分享,错的东西却可能带给别人错误的理解,这一点我确实得向看了我写了一些bug博客的人道个歉。

        针对大牛所指出的错误,晚上翻出了资料,重新温习了一遍。继续总结一下:

       

        一、在JVM中,内存是如何被划分的?

        java把内存分两种:一种是栈内存,另一种是堆内存

        1. 在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配;(所以int的东西放在栈中)

        2. 堆内存用来存放由new创建的对象和数组以及对象的实例变量。

        在函数(代码块)中声明(这里并没有实例化)一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理

 

        二、堆和栈的优缺点比较
        1. 堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的;缺点就是要在运行时动态分配内存,存取速度较慢。

        2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器,栈数据可以共享;但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

 

        三、new出来的对象存在内存的什么地方?

        在Java中,创建一个对象包括对象的声明和对象的实例化两部分。程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(heap)中分配空间。

public class NewTest {
	double aaa;
	double bbb;
	public NewTest(double aa,double bb){
		aaa = aa;
		bbb = bb;
	}
}

        上面的这段代码,当我们使用NewTest  test = new NewTest()时,我们做如下分析:

        NewTest  test : 声明一个对象test时,将在栈内存为对象的引用变量test分配内存空间,但NewTest的值为空,称test是一个空对象。空对象不能使用,因为它还没有引用任何"实体"。

        test = new NewTest()时,在堆内存中为类的成员变量aaa,bbb分配内存,并将其初始化为各数据类型的默认值;接着进行显式初始化(类定义时的初始化值);最后调用构造方法,为成员变量赋值,返回堆内存中对象的引用(相当于首地址)给引用变量test,以后就可以通过test来引用堆内存中的对象了。

 

NewTest  test1 = new NewTest();
NewTest  test2 = new NewTest();

        在使用同一个类创建两个不同的对象的时候,这些对象实例将在堆中被分配到不同的内存空间,改变其中一个对象的状态不会影响其他对象的状态。

 

        四、Java基本类型在内存中的存储

        这里重点讨论一下八种基本类型(int, short, long, byte, float, double, boolean, char)在内存中时如何存放的。我们所指的八种基本类型定义的变量,是指形式如 int a=3、char aa ='a'类型的,即不通过new的。这里的aa时指向char类型的一个引用,指向'a'这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于中。

        另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。

        比如:我们同时定义:

                long a =3;

                long b =3;

        编译器处理的过程是:

        1. 编译器先处理long a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。

        2. 接着处理long b = 3;在创建完b这个引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

        定义完a与b的值后,如果再令a = 4,这时,b不会等于4,还是等于3。在编译器内部,遇到时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

 

        五、基本类型对应的包装类

        基本类型都有对应的包装类:如int对应Integer类,double对应Double类等,基本类型的定义都是直接在栈中,如果用包装类来创建对象,就和普通对象一样了。例如:

        int a=0;a直接存储在栈中。

        Integer b= new Integer(5);b对象数据(这里指的是5)存储在堆中,b的引用存储在栈中,通过栈中的引用来操作对象。

   

        六、String在内存中的存放

        String是一个特殊的包装类数据,可以用用以下两种方式创建:

        String str = new String("abc");第一种创建方式,和普通对象的的创建过程一样;

        String str = "abc";  第二种创建方式,类似于基本数据类型的创建,变量名和数据(更正)存放在栈空间中,数据放在常量池中

 

        七、数组在内存中的存放

         int x[] 或者int []x 时,在内存栈空间中创建一个数组引用,通过该数组名来引用数组。

         x = new int[5] 将在堆内存中分配5个保存int型数据的空间,堆内存的首地址放到栈内存中,每个数组元素被初始化为0.

 

         八、static变量在内存中的存放

         用static的修饰的变量和方法,实际上是指定了这些变量和方法在内存中的"固定位置"-static storage,可以理解为所有实例对象共有的内存空间。static变量有点类似于C中的全局变量的概念;静态表示的是内存的共享,就是它的每一个实例都指向同一个内存地址。把static拿来,就是告诉JVM它是静态的,它的引用(含间接引用)都是指向同一个位置,在那个地方,你把它改了,它就不会变成原样,你把它清理了,它就不会回来了。

         那静态变量与方法是在什么时候初始化的呢?

         对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再进行初始化。

 

——2013年1月29日凌晨写于刘洋寝室

17
9
分享到:
评论
28 楼 jianwwpro 2013-01-31  
MrCrapBag 写道
很佩服楼主的精神,很多情况下,自己不暴露出来,别人不知道你不知道。

你现在正在处于知道自己不知道的进程中。。

我是来学习的。我知道我似懂非懂。
27 楼 ZangXT 2013-01-31  
http://zangxt.iteye.com/admin/blogs/440330
26 楼 stef831018 2013-01-31  
个人认为,楼主的内容在一定条件和使用范围内是正确的,也是一般开发应该关注的,这个实用比重能占到80%,至于各位所说的其他内存空间,其实实际中只有调试阶段和性能测试阶段的个别职位比较关注,更倾向性能优化和内存优化,于开发而言意义不大,实用性占20%,况且如果要谈到比较详细完整的内存空间分配和优化,个人认为不能脱离实际代码和实际情况,需要结合实际的状况分析;另外关于虚拟机的版本,个人认为只要遵守了低版本JVM虚拟机规范,高版本下至少不会有问题,所以还是不要太打击楼主了
25 楼 yxx676229549 2013-01-31  
看看《深入理解Java虚拟机:JVM高级特性与最佳实践》
24 楼 boygirl 2013-01-30  
23 楼 rc77 2013-01-30  
作为入门者,这点基础认识足够了。
当然里面还是有不少的错误,希望博主可以坚持把这篇博客继续下去,每隔一段时间进行补充和修改,呵呵
22 楼 JianbinJava 2013-01-29  

撸主,,
您这是在故意找抽么...
21 楼 junzai 2013-01-29  
正好我也想弄清楚这个问题呢
20 楼 zjuttsw 2013-01-29  
其它不说什么,精神可嘉
19 楼 chenjingbo 2013-01-29  

keke8614 写道
stormhouse 写道
wuliaolll 写道
String str = "abc";  第二种创建方式,类似于基本数据类型的创建,变量名和数据存放在栈空间中。

谁告诉你这个是栈空间,"abc"是对象,怎么可能放在栈里

String str = new String("abc");第一种创建方式,和普通对象的的创建过程一样;

这个也不一样了,和普通对象的创建过程不一样的是它创建了两个对象。

楼主在自己没有弄明白前不要误导人啊……还标红


是的,这块问题我也看出来了,String有intern()方法,创建了两个对象,推荐博主本书《深入理解Java虚拟机:JVM高级特性与最佳实践》
http://product.china-pub.com/194035


补充下,上面说的intern()是在字符串常量池中寻找,如果常量池中没有,就去创建该字符串,并添加到常量池中。关于String str = new String("abc");创建几个对象的说法,为什么创建两个对象的真正原因是:首先看String构造方法public String(String original) {......},第一个是创建了‘abc’的字符串,并把该字符串添加到常量池,第二个是用new创建了str的String对象,并让str指向了‘abc’的对象地址上。其实也就是把创建好的‘abc’传给了String的构造方法创建了str对象。所以说首先创建了'abc'对象然后在把'abc'传递给String的构造方法创建了str对象。在编程中为了提高性能一般使用String str="abc"创建字符串对象。如果想验证常量池的存在,可以利用String的intern()方法进行测试。你可以看下http://www.iteye.com/topic/209904


你说的这个理论是基于jdk6的..jdk7已经在考虑移除PermGen了.第一个被考虑移除的就是StringTable了..所以说呢,如果你使用String的intern方法一直创建对象,在jdk7下是不会造成Pergem溢出的..不信你可以试试.嘿嘿
18 楼 chenjingbo 2013-01-29  
哈哈.大家讨论的起劲,我也无聊来玩玩..

我的感觉是,楼主确实对jvm内存管理的知识太过于欠缺了..楼主可以去http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html 这个是一个jvm规范..

我想你会认为jvm中的内存只包含栈内存和堆内存,确实也反应一个现实就是,一般的程序员确实只关心这两部分内存..但是我需要提出来的是,栈和堆完全是两个东西.根本没必要比谁的速度快..就好像你要把一个球丢出去.你没必要去比脚踢的就比手扔的更远..因为手和脚做的是两个 功能.

我说说个人的理解,

(1)栈是运行时单位,堆是存储单位..栈解决程序运行问题,或者说如何处理数据
堆解决数据存储问题,或者说数据怎么存,存在哪

(2)堆中存的是对象,栈中存的是基本数据类型和堆中对象的引用(还有returnAdress)
一般来说对象的大小是不可估计的.但是栈中所有数据都是固定长度的..其实理论上说,基本数据类型也可以存在堆当中,只是因为基本数据类型是固定数据长度的,所以没必要弄一个引用指向堆中的一个基本数据类型的对象.这样会是一种浪费...但是,我想说的是,栈绝对不是为了给基本数据类型存放而存在的!


最后说一下,不是所有的基本数据类型都存放在栈当中..之前我已经说明,栈只是运行时单位,如果是类成员变量并且是被static 和final 修饰的话,会在Constant Pool中存放一个基本数据..而大家知道,Constant Pool是存放在方法区(MethodArea)中的.不过,按照jvm的规范来说,MethodArea是堆的一部分..不过大家喜欢叫他Non-heap..也是为了让他与堆区分开来..

ps .上面的理论,都是基于jvm实现为sun hotspot .编译器为javac .且那种JIT编译优化(比如栈上分配)除外
17 楼 jcs130 2013-01-29  
引发了这么多讨论也是不错啊~继续加油~谁也不可能一下子就搞懂一个东西~
16 楼 kingj 2013-01-29  
String str=new String("abc");这句代码可能会创建1-2个对象
来看字节码
Code:
  0:   new     #16; //class java/lang/String
  3:   dup
  4:   ldc     #18; //String abc
  6:   invokespecial   #20; //Method java/lang/String."<init>":(Ljava/lang/String;)V
  9:   astore_1
  10:  return

从 4:   ldc可以知道
abc这个字符串是在常量池中分配的,对于hotspot虚拟机来说,常量池数据会放在方法区这块内存之中,同堆一样会可以进行垃圾回收。
然后str这个只是一个引用,可以理解为一个指针指向了堆中实例化的这个变量

如果在执行new String("abc")时,在常量池中找不到abc这个字符串,就会重新在堆中分配一个abc变量
15 楼 keke8614 2013-01-29  
stormhouse 写道
wuliaolll 写道
String str = "abc";  第二种创建方式,类似于基本数据类型的创建,变量名和数据存放在栈空间中。

谁告诉你这个是栈空间,"abc"是对象,怎么可能放在栈里

String str = new String("abc");第一种创建方式,和普通对象的的创建过程一样;

这个也不一样了,和普通对象的创建过程不一样的是它创建了两个对象。

楼主在自己没有弄明白前不要误导人啊……还标红


是的,这块问题我也看出来了,String有intern()方法,创建了两个对象,推荐博主本书《深入理解Java虚拟机:JVM高级特性与最佳实践》
http://product.china-pub.com/194035


补充下,上面说的intern()是在字符串常量池中寻找,如果常量池中没有,就去创建该字符串,并添加到常量池中。关于String str = new String("abc");创建几个对象的说法,为什么创建两个对象的真正原因是:首先看String构造方法public String(String original) {......},第一个是创建了‘abc’的字符串,并把该字符串添加到常量池,第二个是用new创建了str的String对象,并让str指向了‘abc’的对象地址上。其实也就是把创建好的‘abc’传给了String的构造方法创建了str对象。所以说首先创建了'abc'对象然后在把'abc'传递给String的构造方法创建了str对象。在编程中为了提高性能一般使用String str="abc"创建字符串对象。如果想验证常量池的存在,可以利用String的intern()方法进行测试。你可以看下http://www.iteye.com/topic/209904
14 楼 stormhouse 2013-01-29  
wuliaolll 写道
String str = "abc";  第二种创建方式,类似于基本数据类型的创建,变量名和数据存放在栈空间中。


谁告诉你这个是栈空间,"abc"是对象,怎么可能放在栈里



String str = new String("abc");第一种创建方式,和普通对象的的创建过程一样;

这个也不一样了,和普通对象的创建过程不一样的是它创建了两个对象。



楼主在自己没有弄明白前不要误导人啊……还标红


是的,这块问题我也看出来了,String有intern()方法,创建了两个对象,推荐博主本书《深入理解Java虚拟机:JVM高级特性与最佳实践》
http://product.china-pub.com/194035
13 楼 water_lang 2013-01-29  
  String str = "abc";  第二种创建方式,类似于基本数据类型的创建,变量名和数据存放在栈空间中。

这里好是单独放在内存的一块里吧,这个块和static是放在一起的,他没和int a =0这个在栈里
12 楼 lvwenwen 2013-01-29  
1. 新手帖
11 楼 Java小K 2013-01-29  
简单说,栈是用来放引用的(声明的变量),堆是用来放对象的(new出的对象实例);栈的特点是轻量存取速度快空间分配不灵活,堆则是空间运行时动态分配

可以这样理解吗
10 楼 MrCrapBag 2013-01-29  
很佩服楼主的精神,很多情况下,自己不暴露出来,别人不知道你不知道。

你现在正在处于知道自己不知道的进程中。。
9 楼 xussen 2013-01-29  
两个有疑问的地方:
栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器,栈数据可以共享
1、栈的速度仅次于CPU的寄存器? 如果没记错,内存和cpu之间有storebuffer,还有好几层cache。
2、栈数据可以共享?如果没记错,栈是通过栈帧组成,每个方法的调用会创建一个栈帧,栈帧存的只可能是本地变量(栈上分配)
所有的对象都在堆(heap)中分配空间?
1、这个不一定吧,如上面所说的,本地变量有可能通过栈上分配的方式将变量存在栈中,随着方法的退出而消亡,减少垃圾收集的负担

相关推荐

    Java中堆内存与栈内存分配浅析

    本文将深入探讨Java中堆内存与栈内存的分配机制,并通过对比分析它们之间的差异,帮助读者更好地掌握Java内存管理的核心概念。 #### 二、堆内存与栈内存概述 ##### 1. 堆内存 堆内存是Java虚拟机(JVM)用于存储...

    Java中堆内存和栈内存详解

    ### Java中堆内存和栈内存详解 #### 一、引言 Java作为一种广泛使用的编程语言,其内存管理机制是...通过本文的学习,读者应该能够更深入地理解Java内存管理的基本概念,并能够根据实际需求选择合适的内存使用策略。

    Java数据结构之栈与堆

    Java 数据结构中的栈和堆是两种重要的内存管理方式,它们在程序执行时分别扮演着不同的角色。栈主要负责存储程序运行过程中的局部变量、函数调用等信息,而堆则是用于动态分配对象内存的区域。 栈的优势在于其存取...

    Java中栈内存和堆内存详解

    Java中栈内存和堆内存详解,非常容易理解

    Java栈内存与堆内存

    Java把内存划分成两种:一种是栈内存,一种是堆内存。 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过...

    java内存分配之常量池,栈,堆1

    Java内存管理是编程过程中的重要概念,涉及到程序的性能和稳定性。在Java中,内存主要分为四个区域:寄存器、栈、堆和方法区(包括常量池)。以下是这四个区域的详细说明: 1. **寄存器**: 这是计算机硬件的一部分...

    关于Java栈与堆的思考

    关于Java栈与堆的深入解析 Java作为一种广泛使用的编程语言,其内存管理机制是学习者必须掌握的核心概念之一。在Java中,栈(Stack)与堆(Heap)是用于存储数据的主要区域,它们各自承担着不同的职责,对于理解...

    解析Java栈与堆

    Java栈与堆的存储机制解析 Java栈和堆是Java语言中两个最基本的存储机制,它们都是Java用来在RAM中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。 1. 栈的存储机制: 栈的优势是...

    JavaSE基础篇 -- jdk配置,数组及其应用,栈和堆内存图解(Java源码)

    栈和堆是Java内存管理中的两个关键区域。栈主要用于存储局部变量、方法调用信息和常量。每次方法调用都会在栈上创建一个新的栈帧,当方法执行完毕,栈帧会被弹出,其内的变量也随之消失。栈内存的分配和释放非常快速...

    Java中的堆和栈

    #### 四、堆与栈的区别 1. **生命周期**:栈内存中的变量在其定义的方法执行结束后就会被销毁;而堆内存中的对象直到没有引用指向它才会被垃圾回收。 2. **内存分配**:栈内存分配和释放速度快,而堆内存的分配涉及...

    java栈与堆1

    本文将详细阐述Java和C++中栈与堆的区别以及Java在这方面的优势。 首先,我们来理解栈内存。栈内存主要负责存储程序运行过程中的局部变量,包括基本类型变量(如int、char)和对象的引用变量。在Java中,当函数执行...

    Java 中的堆和栈

    在Java中,内存主要分为两个区域:栈内存和堆内存。这两部分内存各自有不同的特点和用途。 首先,栈内存主要负责存储基础数据类型(如byte, short, int, long, float, double, boolean, char)和对象的引用。当在...

    Java堆和栈的区别

    Java 堆和栈是 Java 中的两种内存管理机制,它们都是 Java 用来在 RAM 中存放数据的地方。但是,它们有很多不同之处。 Java 堆是一个运行时数据区,类的对象从中分配空间。这些对象通过 new、newarray、anewarray ...

    详解java堆和栈

    ### 详解Java堆和栈 #### 一、引言 在Java编程中,理解堆(Heap)和栈(Stack)的概念及其区别对于程序员来说至关重要。本文将深入剖析这两个概念,并探讨它们之间的差异以及如何影响程序的运行。 #### 二、Java...

    Java堆、栈和常量池——内存剖析

    ### Java堆、栈和常量池——内存剖析 #### 寄存器 寄存器作为最快的存储区域之一,由编译器自动管理分配与回收,它位于CPU内,用于存储临时变量,例如局部变量和一些操作数。由于寄存器的数量有限且由编译器自动...

    区别Java中堆与栈区别Java中堆与栈

    Java 中堆与栈的区别 Java 中的堆和栈是两个不同的内存区域,分别用于存放不同类型的数据。堆是一个运行时数据区,类的对象从中分配空间,通过new、newarray、anewarray 和 multianewarray 等指令建立,垃圾回收器...

Global site tag (gtag.js) - Google Analytics