前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

Java数据类型在执行过程中存储在两种不同形式的内存中:栈和堆,它们通常由运行Java虚拟机(JVM)的底层平台维护。本文从Java软件开发的角度提供了对这两种内存类型的一些见解。

Java程序是怎么运行的

Java程序运行在Java Virtual Machine (JVM)中,JVM提供了Java应用程序在运行时所需要的任何资源的管理器。这就意味着开发者写的应用程序或者创建的应用程序没有能力去直接获取系统资源(不管是硬件还是软件),除非JVM能提供给这些资源。所以在Java中,程序运行顺序如下图:

JVM层使得Java平台能够独立运行,其他编程语言,例如C/C++没有使用类似JVM层的东西,因此它们不是跨平台的语言,即使它们是可移植的语言。它们就像下图一样:

这两种形式有优点也有缺点,Java已经有了自己的生态系统。与此同时,像C/C++这样的编程语言能够直接访问系统资源,从而更有利于优化核心单元的使用,从而产生超级快速和高效的程序。但两者在软件开发领域都有各自的用途。

所有编程语言在编译和执行过程中都有许多相似之处。其中最重要的一点就是内存管理,无论使用哪种语言,内存管理对程序的整体效率都有重要影响,因为管理好内存资源,从而才能管理好应用程序性能。

Java中的运行内存

应用程序之间的一个常见现象是,每个应用程序都需要一些内存才能以最佳方式工作,该内存由底层平台提供。在Java中,JVM提供了这些内存资源(当然需要操作系统授权)。Java中,JVM内存主要分为5个部分分别为:方法区、堆、栈、PC寄存器和本地方法区。

本文主要关注堆和栈。内存不像一张白纸,程序员只需要草草记下就可以存储数据,在使用内存之前,需要对其进行结构化。栈和堆是使用内存时遵循的数据结构,在程序执行期间,存储的数据用于各种目的,这取决于程序的目的是什么。

JVM决定程序执行期间使用的运行时数据区域。有些数据区域是依赖于JVM的,这意味着它们是在JVM启动时创建的,并在JVM的整个生命周期中持续存在。但是,每个线程都创建和销毁其他数据区域。JVM可以同时执行多个执行线程,这意味着每个线程都有自己的pc(Program Counter,程序计数器)来维护正在执行的当前指令的位置,还有一个栈帧来保存静态内存分配。

栈是内存中的一种结构,开发人员在其中存储元素,其方式允许只从栈顶检索数据——通常称为先入后出(FILO或LIFO)。因为每个线程都维护一个私有的JVM栈,它被用来存储与它们的静态内存分配相关的变量。特定于我们在代码中声明和使用的方法的原语变量实际上存储在栈区域中。另外,对实际存储在堆内存中的对象的引用也存储在堆栈区域中。因此,任何本地分配的内存都存储在堆栈中。

堆栈内存的默认大小可以使用JVM参数-Xss来更改。有时,如果分配了太多变量或方法递归调用自身,则堆栈可能溢出。所有Java程序员都知道的一个常见错误是Java.lang.stackoverflowerror,当栈内存不足时提示该错误。Java中的每个方法调用都会在栈中分配一块内存,因此,设计糟糕的递归方法调用很容易占用所有栈内存,导致栈内存溢出错误。

堆是JVM一启动就创建的内存区域,它会一直存在,直到JVM被销毁。与栈不同的是,栈是单个线程的属性(因为每个线程都有自己的栈),堆实际上是由JVM本身管理的全局内存,此内存在运行时用于为对象分配内存。因此,对象的实例化可以是用户定义的类、JDK或其他库类。简而言之,使用new关键字创建的任何对象都存储在堆内存中。堆内存中的对象可被JVM运行的所有线程访问。访问管理非常复杂,使用了非常复杂的算法,这就是JVM垃圾收集器发挥作用的地方。

堆的默认大小可以使用JVM参数-Xms-Xmx来更改。随着对象的创建和销毁,堆的大小也会增加或减少,如果达到最大内存限制后并尝试进一步分配内存,则抛出java.lang.OutOfMemoryError

堆中的字符串池(StringPool)

Java.lang.String类是Java中使用最多的类,因此,应该特别注意它的效率问题。与基本数据类型相比,字符串的操作效率总是很慢,所以,必须采用某种方式使得字符串对象操作的效率和便利性方面类似或者接近于基本数据类型,为了达到这个目的就在堆中分配了一块特殊内存区域(StringPool),创建的任何字符串对象都由JVM存储在StringPool中。与堆中创建的其他对象相比,这提高了性能。

从代码示例说明堆和栈

为了更好地说明在Java中堆和栈内存的使用,让我们写一个简单的程序,并决定哪个分配分配到哪个内存——堆或栈:

public class HeapAndStackTest {    public static void main(String[] args) {        int x=10;        int y=20;        String greet = "Hello";        Date d = new Date();        diff(x, y);    }    public static int diff(int x1, int x2) {        return x2-x1;    }}

这段代码运行方式如下:

  • 程序启动,JVM将Java运行时环境(JRE)类加载到堆中。
  • 在遇到main() 方法时,会创建一个栈帧。
  • 局部变量xy存储在栈中。
  • 字符串greet分配在堆的StringPool区域中。
  • Date对象分配在堆区,而它的引用d存储在栈中。

总结

栈和堆是Java程序在代码执行期间使用的两个区域。除了这两个之外,还有其他内存区域,如方法区域、寄存器、本地方法域等等。每种区域在Java应用程序中都有其特定的用途。但是,从程序员的角度来看,栈和堆是JVM必须理解的区域。

参考资料:
https://www.developer.com/java/stack-heap-java-memory/