程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程所执行代码的行号指示器,主要是为了在程序运行期间,线程切换后能够恢复到正确的执行位置。每条线程都有一个独立的程序计数器,不同线程的程序计数器之间互不影响、独立存储。

如果当前线程执行的是Java方法,则程序计数器记录的是正在执行的字节码的指令地址,如果当前线程执行的是本地方法,则程序计数器的值为空。

虚拟机栈

虚拟机栈描述的是Java方法执行的线程内存模型,每一个Java方法从被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈帧中存储着局部变量表、操作数栈、动态连接、方法出口等信息。

局部变量表存放了Java基本数据类型(byte,short,int,long,float.double,char,boolean)、引用类型(对象的引用)和returnAddress类型的数据

这些类型的数据在局部变量表中的存储空间,以局部变量槽来表示。64位长度的long和double类型的数据,占用两个局部变量槽,其他类型的数据占用一个局部变量槽。局部变量表所需的内存空间在编译期间完成分配,方法执行期间不会改变局部变量表的大小。

本地方法栈

本地方法栈和虚拟机栈类似,虚拟机栈为Java方法服务,本地方法栈为本地方法服务。

不同于程序计数器、虚拟机栈、本地方法栈随线程的创建而分配,随线程的销毁和释放,Java堆在虚拟机启动时创建,是Java虚拟机管理的做大的一块内存。

Java堆唯一的目的就是存放对象实例。

Java堆是垃圾收集器管理的区域。

与Java堆相关的一些诸如“老年代”、“新生代”、“Eden空间”、“Survivor空间”等区域划分,仅仅是一部分垃圾收集器的共同特性和设计风格,并不是《Java虚拟机规范》堆Java堆的进一步划分。

从内存分配的角度看,所有线程共享的Java堆可以分为多个线程私有的分配缓冲区,以提升内存分配的效率。并且,无论从什么角度、如何进一步划分,Java堆的作用都只能是存储对象实例,进一步的划分仅仅是为了更方便的分配和回收内存。

方法区

方法区同Java堆一样,在虚拟机启动时创建,线程共享,

方法区用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

方法区在《Java虚拟机规范》中被描述为Java堆的一个逻辑部分,但是它却有一个别名叫做 "Non-Heap".

很多人习惯将方法区称为 “永久代”(Permanent Generation),因为hotSpot虚拟机的方法区是用永久代实现的。

从JDK1.8开始,hotSpot放弃使用永久代实现方法区,改为使用本地内存,使用永久代实现方法区有两个明显的缺点:

  1. 永久代的大小有 -XX:MaxPermSize 的上限,不设置也有默认的上限。
  2. 有一些方法会因为永久代的原因导致在不同的虚拟机下有不同的表现,例如:String::intern()

世界上并没有完美的程序,但我们并不因此而沮丧,因为写程序本来就是一个不断追求完美的过程。

常量池:

常量池是方法区的一部分内存。常量池在编译期间就将一部分数据存放于该区域,包含基本数据类型如int、long等以final声明的常量值,和String字符串、特别注意的是对于方法运行期位于栈中的局部变量String常量的值可以通过 String.intern()方法将该值置入到常量池中。

全局变量与局部变量

局部变量:

在方法中声明的变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因。

在方法中生明的变量可以是基本类型的变量,也可以是引用类型的变量:

  • 当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在方法栈中。
  • 当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在方法的栈中,该变量所指向的对象是放在堆类存中的

全局变量:

在类中声明的变量是成员变量,也叫全局变量,放在堆中的。

同样在类中声明的变量即可是基本类型的变量,也可是引用类型的变量:

  • 当声明的是基本类型的变量,其变量名及其只时放在堆类存中的。
  • 当声明的是引用变量时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。

堆、栈和方法区存储数据的区别

栈:为即时调用的方法开辟空间,存储局部变量值(基本数据类型),局部变量引用。当一段代码或者一个方法调用完毕后,栈中为这段代码所提供的基本数据类型或者对象的引用立即被释放;注意:局部变量必须手动初始化。

堆:存放引用类型的对象,即new出来的对象、数组值、类的非静态成员变量值(基本数据类型)、非静态成员变量引用。其中非静态成员变量在实例化时开辟空间初始化值。

方法区:存放class二进制文件。包含类信息、静态变量,常量池(String字符串和final修饰的常量值等),类的版本号等基本信息。静态成员变量是在方法区的静态域里面,而静态成员方法是在方法区的class二进制信息里面(.class文件和方法区里面的二进制信息不一样,读取.class文件按照虚拟机需要的格式存储在方法区,这种格式包括数据结构方面。)因为是共享的区域,所以如果静态成员变量的值或者常量值(String类型的值能够非修改)被修改了直接就会反应到其它类的对象中。

从堆和栈的功能以及作用来比较,堆和栈有什么不同?

堆主要用来存放对象,栈主要是用来执行程序的。相较于堆,栈的存取速度更快,但栈的大小和生存周期必须确定,因此缺乏一定的灵活性。而堆却可以在运行时动态的分配内存,生存期不用提前告诉编译器,但这也导致了其存取速度的缓慢。

不同线程调用方法为什么是线程安全的?

任何方法每次被线程调用,都会在栈中开辟新的空间。同一方法的不同线程执行,方法与方法之间互不影响。全局变量因为是存在堆区的对象中,所以会互相干扰。

成员变量存储在哪儿?

静态成员变量存储在方法区,非静态成员变量存储在堆区。

为什么局部变量不能够static修饰?

局部变量存储在栈区,在方法调用时不能够自动初始化必须由程序员手动初始化,否则会报错,归根结底是由于static变量和局部变量存储的位置不一样。

最后修改:2022 年 04 月 03 日
如果觉得我的文章对你有用,请随意赞赏