在JAVA的内存空间中,存在重要的两个内容便是JAVA的堆和栈,两者都用来存放数据,但又有所区别。
当你用各种工具使用java语言编写了一段程序,当这段程序被运行的时候,在java中的内存会有五块区域进行数据的存储,分别为程序计数器,本地方法区,方法区,堆,栈。
1.堆
就相当于当你new一个对象的时候,就会分配一个堆内存给你,当对象销毁时就会有垃圾回收机制来回收这个对象的堆空间。
2.栈
就好比一串珠子,你只能从一头加或者取,要取后面的就要把前面的取出来才可以。
3.堆内存
作用就是用来存放java中的对象和数组,当new一个对象或者new一个数组的时候就会在堆内存中开辟一个空间给它。用于存放。
特点:第一个就是先进先出,后进后出,类似于一串珠子,一头来放,一头来出,先放进去的珠子可以先出来,后放进去的珠子要等前面的珠子出来以后才能进去。
堆中的数据都具用默认的初始值。
第二个 堆可以动态的分配内存大小,生存期事先不必告诉编译器,因为他是在运行的时候动态的分配内存,创建的对象当不再被指向时,将会被回收内存,内存回收需要通过jvm的自动垃圾回收器来管理,而C语言需要手动的清楚,这是java优于c的表现之一。
4.栈内存
存在与java中的另一种内存,主要用来执行程序用的,比如基本类型的变量和对象的引用变量。
它是由许多栈帧组成,而每个栈帧又包括了局部变量表、操作数栈、动态链接以及方法出口信息。每次方法调用都会将对应的栈帧压入虚拟机栈,当方法调用结束(方法调用return或者方法抛出异常)又会将该栈帧从虚拟机栈中弹出。由于栈的特性(FILO 先进后出,后进先出),每次操作的都是栈顶栈帧,又被称为“当前活动栈帧”,代表当前正在执行的方法。在JVM执行引擎运行时,所有指令都针对于当前活动栈帧进行操作。
特点:”先进后出,后进先出
栈的生存空间需要提前声明,而栈中数据的生存空间存在于{}大括号内,也就解释了为什么定义方法或者主函数的时候先要把方法体括起来。
栈中的数据都没有初始值,需要手动的赋予。
存取速度比堆要快,仅次于寄存器。栈数据可以共享,主要表现为
Int a = 123, int b = 123。这里的a和b 指向的栈中的内存是一致的。
缺点:存在栈中的数据大小与生命期是确定的,缺乏灵活性。、
同样栈内存也需要通过jvm的自动垃圾回收器自动回收。
5.栈和堆的区别
Jvm是基于堆栈的虚拟机,每当一个线程创建,jvm就会为其分配一个堆栈,也就是程序的运行就是通过堆栈的操作来进行的,堆栈以帧为单位保存线程的状态,jvm对堆栈只进行两种操作,以帧为单位的压栈和出栈操作。
差异:堆用来存放new的对象或者数组
栈内存用来存放方法或者局部变量,所有的基本变量和引用类型也是。
栈是线程私有的而堆是线程共有的,也就是说不同的线程可以得到相同的对象。
堆中的数据即便与栈中的数据相同,也不会与栈中的数据共享。
在使用 String str = “abc” 时,他的操作原理是
首先会在栈中查找是否含有相同值的栈内存,如果没有则会开辟一个栈内存用来存在这个字符串的值,并且会生成一个地址,相当于每个人家里的门牌号,便于别人能够更快的找到你,接着会创建一个对象假设为A,A对象所包含的就是你的门牌号,也就是栈的地址,通过A对象就可以快速的访问这个地址中的信息。如果在定义这个变量的时候发现栈中存在相同的信息值,则会将A对象给str这个引用,调用str就会使用A对象,也就得到了其值“abc”,这就是栈的数据共享原理。
这里的str并不能完全的说我们创建了string对象,因为由于栈的数据共享性,你可能只是为A对象创建了一个新的引用。
而对于string str = new string(“abc”),则会一律在堆内存中开辟空间用来存放数据,单例模式就是为了解决这种浪费产生的方法。

另外每当定义的字符串改变时都会产生一个新的对象,这个时候应当使用stringBuffer类进行操作在原存储空间中改变值得大小,提高程序效率。
定义一个函数如图:在主函数中声明一个变量为x,将x作为值进入show方法,最后还是输出x的值为1.

原理如下图所示:

当声明一个int变量的时候,在java栈中会为其开辟一个内存空间用来存储X的引用值,当X作为初始值传入show函数的时候,1 的值引用多了一个b,这个时候b和X指向同一个值为1,当对b引用进行操作的时候,java栈便会开辟一个新的地址来存储新的值,并把这个地址交给b,这个时候X引用指向的值还是没有发生变化的。所以输出的值仍然是1.
再看另一种代码展示方式:该程序存在两个函数主函数main和自定义函数show。
这种程序的输出结果仍然是1.

分析该程序,当程序运行的时候JVM将会先执行main函数,因为他是程序的入口,运行主函数时,主函数中定义了一个变量X。这时在栈中便会开辟一个内存地址用来存储1这个值,并且将引用X指向该内存地址。
当运行到show函数时,同理,在函数中会开辟一个内存地址用来存储2这个值,引用X指向这个内存地址。
当show函数执行完毕,这个时候show函数在栈中的生命周期已经结束,其在栈中的内存空间被释放。如图所示。

对于数组的定义在堆栈中的操作

程序运行,jvm在栈内存中开辟空间用来存放array变量,同时在堆中也会开辟一段连续空间用来存储new int[3].同时会给栈一个地址,使得栈可以根据这个地址来找到堆中的数组信息。这时的array变量便是指向堆中的数组信息,
而后第二步对数组中的数据进行赋值,即通过array这个引用找到数组的数据,然后对相应的空间进行数值的赋予,如果不给数组赋值的话,默认int数组的默认值为0;原理图如下

这时再添加一条代码array= null;就会将栈中的引用array清楚,这个时候在堆中的数组数据没有指向的时候就会被JVM的自动垃圾回收装置清除。
再定义一个新的数组array1

这个时候的堆栈图如下图所示:

在这个时候,在栈中定义了新的变量array1,然后将变量array的内存地址分配给array1,所以这个时候array1也将指向array定义的数组,并且根据array1引用来修改数组,array引用指向的数组也会发生变化。这时的输出为20。
在此基础上新添一句语句array1 =null;此时只是数组指向的两个引用少了一个,并不影响数组继续存在在堆中,同样也不会被自动垃圾回收机制回收。
程序计数器为线程私有,生命周期伴随着线程的生命周期,是一块较小的内存空间,用于存放下一条指令所在单元的地址的地方。每执行一条指令,程序计数器就会加一。每个线程都会维护一个独立的程序计数器且各线程之间的程序计数器互不影响,在程序执行过程中,线程会不断的切换,独立的线程计数器保证了当前线程的正确执行位置。程序计数器是唯一一个不会出现OutOfMemoryError(内存溢出)的内存区域,它随着线程的创建而创建,随着线程的结束而消亡。
本地方法区: native关键字修饰的方法被称为本地方法,当线程调用本地方法时,会在本地方法栈中压入当前本地方法的栈帧。该栈帧中包含本地方法的局部变量表、操作数栈、动态链接、方法出口信息。当方法执行完毕时,栈帧会从本地方法栈中弹出,与虚拟机栈相同也会出现StackOverFlowError(堆栈溢出)与OutOfMemoryError(内存溢出)错误。
在JDK1.6时,HotSpot JVM采用Method Area方法区来储存这些数据,也叫永久代(持久代)。
方法区与永久代(持久代)的区别:
方法区是JVM的规范,永久代(持久代)是JVM规范的一种实现
只有HotSpot JVM有HotSpot JVM,对于其他类型的虚拟机例如J9(IBM)、JRockit(Oracle)都没有
方法区是连续的堆空间,当加载的类信息容量超过了最大可分配空间,会引发OutOfMemoryError错误,永久代(持久代)的GC与老年代捆绑,只要其中一个内存空间不足,就会触发永久代(持久代)与老年代的垃圾收集
JDK1.7时将字符串常量池、静态变量转移到了堆区
JDK1.8时采用MetaSpace代替了永久代(持久代)
元空间与永久代(持久代):
相同点 都是对JVM规范方法区的一种实现
不同点 永java堆和栈基础代码久代(持久代)在虚拟机中,元空间在本地内存
永久代(持久代)内存受永久代(持久代)的GC(垃圾收集器)与老年代的内存空间限制,元空间大小受本地内存限制
Java8后HotSpot JVM为什么要删除永久代(持久代)?
由于永久代(持久代)内存受限范围较小,经常会发生内存溢出
由于JRockit VM没有永久代(持久代),移除HotSpot JVM的永久代(持久代)可以促进HotSpot JVM与JRockit VM的融合
以下内容参考至:
http://lhc1986.iteye.com/blog/
http://www.cnblogs.com/xhr8334/archive/2011/12/01/2270994.html
http://ifeve.com/jvm-yong-generation/
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/1502.html