先说一下栈和堆栈,我们听老一辈的程序员一般都会说堆栈,其实栈就是堆栈的意思,连着叫只是由于历史的原因。博主04年学数据结构的时候,书上都是说的堆栈,FILO(先进后出),也经常拿堆栈和队列,FIFO(先进先出)这两种数据结构拿来作比较。

系统中的堆、栈 和 数据结构堆、栈

系统中的堆、栈和数据结构堆、栈不是一个概念。可以说系统中的堆、栈是真实的内存物理区,数据结构中的堆、栈是抽象的数据存储结构。

数据结构堆、栈

1. 栈:实际上就是满足后进先出的性质,是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。

2. 堆:堆是一种完全二叉树或者近似完全二叉树,完全二叉树是效率很高的数据结构,像十分常用的排序算法、Dijkstra算法、Prim算法等都要用堆才能优化。

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。详见:十大经典排序算法

系统方面的堆和栈

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区(heap)— 是一个可动态申请的内存空间(其记录空闲内存空间的链表由操作系统维护),在java中,所有使用new xxx()构造出来的对象都在堆中存储一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

堆是全局的,堆栈是每个函数进入的时候分一小块,函数返回的时候就释放了,静态和全局变量,new得到的变量,都放在堆中,局部变量放在栈中,所以函数返回,局部变量就全没了。

我们今天重点讲的是Java里的堆和栈也就是系统方面的堆和栈。

Java里的堆、栈和常量池

(下面以图文的方式讲解,方便大家理解)


1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

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

堆的优势是可以动态地分配内存大小,所有使用new xxx()构造出来的对象都在堆中存储,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

3. 常量池:存放字符串常量和基本类型常量(public static final)。

常量池的好处是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。

例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。

(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。

(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

4. Java中的数据类型有两种。

一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,不包含String)。

如int a = 3; 这里的a是一个指向int类型的引用,指向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的值。

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

4. String是一个特殊的包装类数据。

即可以用String str = new String("abc"); 的形式来创建也可以用String str = "abc"; 的形式来创建String str = new String("abc"); 的形式来创建

也可以用

String str = "abc"; 的形式来创建

前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。

作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换。

Java 中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单 例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。

那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

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

(1) 先定义一个名为str的对String类的对象引用变量放入栈中。

(2) 在常量池中查找是否存在内容为"abc"字符串对象。
(3) 如果不存在则在常量池中创建"abc",并让str引用该对象。
(4) 如果存在则直接让str引用该对象。

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

String str1 = "abc"; String str2 = "abc"; System.out.println(str1==str2); //trueSystem.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 = "bcd"; System.out.println(str1 + "," + str2); //bcd, abc System.out.println(str1==str2); //false 

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

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

再修改原来代码:

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

str1 = "bcd"; 

String str3 = str1; System.out.println(str3); //bcd 

String str4 = "bcd"; System.out.println(str1 == str4); //true 

str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用 str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。

6. 我们再来看看 String str = new String("abc")创建过程

(1) 先定义一个名为str的对String类的对象引用变量放入栈中。

(2) 然后在堆中(不是常量池)创建一个指定的对象,并让str引用指向该对象。

(3) 在常量池中查找是否存在内容为"abc"字符串对象。
(4) 如果不存在,则在常量池中创建内容为"abc"的字符串对象,并将堆中的对象与之联系起来。
(5) 如果存在,则将new出来的字符串对象与字符串常量池中的对象联系起来(即让那个特殊的成员变量value的指针指向它)

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

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

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

对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

这也就是有道面试题:String s = new String(“abc”);产生几个对象?答:一个或两个,如果常量池中原来没有”abc”,就是两个。

7. 基本数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的基本数据数据类型包装类都不能更改其内部的值。

8. String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;

String str1 = "abc";  String str2 = new String("abc"); String str3 = str2.intern();System.out.println( str1==str2 );  //falseSystem.out.println( str1==str3 );  //trueString str3 = str2.intern();

System.out.println( str1==str2 );  //falseSystem.out.println( str1==str3 );  //true

9. 结论与建议:

(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类,以提高程序效率。

申请响应

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

申请限制

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

堆和栈的区别用比喻来看:

使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。

使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

堆栈缓存方式

栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。

堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

在JAVA中,有六个不同的地方可以存储数据:

 
1. 寄存器(register):这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。

2. 栈(stack):存放基本类型的变量数据和对象的引用。位于通用RAM中,但通过它的“堆栈指针”可以从处理器哪里获得支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性。

3. 堆(heap):一种通用性的内存池(也存在于RAM中),用于存放所有的JAVA对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区 域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行 这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代价,用堆进行存储分配比用堆栈进行存储存储需要更多的时间。

4. 静态存储(static storage):这里的“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。

5. 常量存储(constant storage):存放字符串常量和基本类型常量(public static final)。 常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。

6. 非RAM存储:硬盘等永久存储空间。如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。

就速度来说,有如下关系: 
寄存器 < 堆栈 < 堆 < 其他 

『上面这段话摘取之《Thinking in Java》』

陛下...看完奏折,点个赞再走吧!

推荐阅读

技术:前后端分离--整套解决方案

技术:Java面试必备技能

技术:docker私有仓库搭建,证书认证,鉴权管理

技术:如何成功抢到回家的火车票!

技术:Linux 命令行快捷键

技术:超详细黑苹果安装图文教程送EFI配置合集及系统

技术:恐怖的广告推送。其实,我们每天都在“裸奔”!

技术:如果我不说,你能看出这个主播不是真人吗?

技术:IaaS,PaaS和SaaS,QPS,RT和TPS,PV,UV和IP到底是什么意思?

技术:项目多环境切换——Maven Profile

工具:如何通过技术手段 “干掉” 视频APP里讨厌广告?

工具:通过技术手段 “干掉” 视频APP里讨厌的广告之(腾讯视频)

博主12年java开发经验,现从事智能语音工作的研发,关注微信公众号与博主进行技术交流!更过干货资源等你来拿!

java堆、栈、堆栈,常量池的区别,史上最全总结相关推荐

  1. java堆和栈 常量池_GitHub - han-guang-xue/difference-of-stack-heap-pool: Java中堆、栈和常量池的区别...

    Java中堆.栈和常量池的区别 栈 堆 常量池的概念 首先我们先了解一下概念,Java把内存分成两种,一种叫做栈内存,一种叫做堆内存. 栈内存 存放基本类型的变量数据和对象类型的引用(请注意存放的是引 ...

  2. “堆”,栈,堆栈,队列,它们的区别?

    2019独角兽企业重金招聘Python工程师标准>>> 什么是"堆","栈","堆栈","队列",它们 ...

  3. 什么是“堆”,栈,堆栈,队列,它们的区别

    如果你学过数据结构,就一定会遇到"堆","栈","堆栈","队列",而最关键的是这些到底是什么意思?最关键的是即使你去面 ...

  4. Java中堆、栈和常量池的区别

    转自: https://blog.csdn.net/qq_45121279/article/details/91446764

  5. Netty内存池 (5w长文+史上最全)

    文章很长,建议收藏起来慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 经典图书:<Java高并发核心编程(卷1)> 面试必备 ...

  6. Java 5~11各个版本新特性史上最全总结

    Java 5 Java5开发代号为Tiger(老虎),于2004-09-30发行 特性列表 泛型 枚举 自动装箱拆箱 可变参数 注解 foreach循环(增强for.for/in) 静态导入 格式化( ...

  7. 史上最全java架构师技能图谱(上)

    java架构师最全技能图谱上篇,包含:数结构算法.java进阶.web开发.框架与工具四大技能图谱. 下篇将包含大数据以及性能.设计模式.UML.中间件.分布式集群.负载均衡.通讯协议.架构设计等技术 ...

  8. 史上最全讲解:JAVA中的Map与Thread

    史上最全讲解:JAVA中的Map与Thread 文章目录 史上最全讲解:JAVA中的Map与Thread Map HashMap TreeMap Properties Thread 开启多线程方法1 ...

  9. 史上最全讲解:JAVA中的方法 数组 类

    史上最全讲解:JAVA中的数组 方法 面向对象 文章目录 史上最全讲解:JAVA中的数组 方法 面向对象 数组 数组的定义: 数组的特点: 数组的初始化: 数组的遍历: 从前到后拿到每一个数据 方法 ...

  10. 史上最全java架构师技能图谱(下)

    "java架构史上最全技能图谱分为上下两篇,这是java架构史上最全图谱下篇,包含: 大数据以及性能.设计模式.UML.中间件.分布式集群.负载均衡.通讯协议.架构设计等技术图谱等章节. 如 ...

最新文章

  1. 求求你们了,别再写满屏的 try catch 了!!
  2. 面向高精度领域的视觉伺服算法汇总
  3. 裸设备 linux,Linux平台下裸设备的绑定:
  4. freemarker中 感叹号、双感叹号、问号、双问号 的使用方法
  5. 判断语句_判断和语句?
  6. LiveVideoStack线上分享第三季(十一):Xilinx视频解决方案
  7. 计算机资源管理器课件,计算机基础课件ppt-第5讲控制面板与资源管理器.pdf
  8. 算法的力量万变不离其宗 -- 李开复
  9. wxpython制作表格界面_Python wxPython库使用wx.ListBox创建列表框示例
  10. 746. Min Cost Climbing Stairs
  11. python微信抢票脚本_春节到了 教你使用python来抢票回家
  12. android开发中常用的占位符
  13. 4. 从IPv4到IPv6
  14. WordCloud生成卡卡西忍术词云
  15. 2022跨境电商新战场:海外社交电商发展前景分析
  16. Multisim10在51单片机仿真中的运用
  17. 物业保安社区安全员巡更巡逻人员定位管理系统
  18. 电脑反复出现蓝屏重启现象
  19. android WebView详解,常见漏洞详解和安全源码
  20. SAP-FICO-银行承兑汇票相关操作,收票入账,背书转让,贴现

热门文章

  1. Photoshop 基础知识 与 常用 快捷键
  2. bf难得梦见我一回,可惜。。。
  3. RuntimeError: CUDA out of memory. Tried to allocate ... MiB Pytorch模型测试时显存一直上升导致爆显存
  4. c语言 怎么输出结点,c语言 线索二叉树 输入结点 输出前驱后继,大神们救命啊!!1...
  5. 附完整python源码)基于tensorflow、opencv的入门案例_发票识别一:关键区域定位
  6. html的style元素
  7. 改变windows10的界面语言
  8. YOLOV5测试代码test.py注释与解析
  9. MySQL的进阶查询
  10. 你还在手写接口文档?来,壹哥教你一招实现在线接口文档