文章目录
- 堆和栈的概念和区别
- 栈 (Stack)
- 堆 (Heap)
- 详细描述
- 补充说明
- 逃逸分析 (Escape Analysis)
- 栈上分配 (Stack Allocation)
- 堆碎片化 (Heap Fragmentation)
堆和栈的概念和区别
堆和栈的概念和区别【改编自博客】
在说堆和栈之前,我们先说一下JVM(虚拟机)内存的划分:
Java程序在运行时都要开辟空间,任何软件在运行时都要在内存中开辟空间,Java虚拟机运行时也是要开辟空间的。JVM运行时在内存中开辟一片内存区域,启动时在自己的内存区域中进行更细致的划分,因为虚拟机中每一片内存处理的方式都不同,所以要单独进行管理。
JVM内存的划分有五片:
- 寄存器;
- 本地方法区;
- 方法区;
- 栈内存;
- 堆内存。
栈 (Stack)
- 存储局部变量:栈主要用于存储局部变量,包括方法参数、局部变量等,for循环内部定义的也是局部变量。这些变量通常是在方法调用开始时创建,在方法结束时销毁。
- 方法调用:每当一个方法被调用时,都会为该方法创建一个新的栈帧来保存局部变量表、操作数栈、动态链接信息以及返回地址等。
- 快速分配与释放:由于栈内存的管理是线程性的且按照后进先出的原则工作,因此分配和回收的速度非常快。
- 生命周期短暂:栈中的数据生命周期较短,当它们的作用域结束时(例如,方法执行完毕),这些数据就会被自动释放。
堆 (Heap)
- 存储对象实例:堆主要用于存储由 new 关键字创建的对象实例以及数组。每个对象都有一个指向其类元数据的指针。
- 动态分配:堆内存中的空间是在运行时动态分配的,并且对象可以长期存在,直到被垃圾回收器回收。
- 垃圾回收:Java 通过自动垃圾回收机制来管理堆内存,当对象不再被引用时,它们会被标记为垃圾并最终被回收。
- 非线程私有:与栈不同,堆内存是所有线程共享的,这意味着所有线程都可以访问堆中的对象。
详细描述
下面我们通过一个图例详细讲一下堆和栈:
比如主函数里的语句 int [] arr=new int [3];
在内存中是怎么被定义的:
在Java中,程序的执行从main方法开始。main方法是程序的入口点,它被定义为静态方法,位于类的内部。当JVM启动并准备执行这个方法时,它会为main方法创建一个新的栈帧,并将其压入调用栈中。栈帧是调用栈的一部分,用于存储局部变量、参数以及返回地址等信息。
当main方法被调用时,一个新的栈帧被创建并压入栈中。这个栈帧包含main方法的局部变量表、操作数栈、动态链接和方法出口信息。局部变量表用于存储方法的参数和局部变量,操作数栈用于执行方法中的操作,动态链接用于确定方法调用的目标,方法出口信息用于处理方法的返回和异常。
在main方法中定义的变量arr是一个引用,它指向堆中创建的数组对象。数组对象通过new关键字在堆中创建,并分配内存空间。堆中的数组对象会被初始化,数组元素根据其类型被赋予默认值。堆中的对象通过内存地址来访问,这个地址是连续的二进制值。栈中的变量(引用)保存了这个内存地址,通过这个引用可以访问堆中的对象。
总结来说,main方法被压入栈中是因为它是程序的入口点,需要一个栈帧来存储其局部变量、参数和返回地址等信息。在main方法中定义的变量arr是一个引用,它指向堆中创建的数组对象。数组对象通过new关键字在堆中创建,并分配内存空间。堆中的数组对象会被初始化,数组元素根据其类型被赋予默认值。堆中的对象通过内存地址来访问,这个地址是连续的二进制值。栈中的变量(引用)保存了这个内存地址,通过这个引用可以访问堆中的对象。
那么堆和栈是怎么联系起来的呢?
在Java中,arr是一个引用变量,它指向堆中创建的数组对象。当arr需要操纵数组时,它通过内存地址来访问堆中的数组对象,而不是直接将数组对象赋给arr。这种数据类型被称为引用数据类型,因为它引用了堆内存中的实体。
在C或C++中,指针的概念与Java中的引用类似。指针是一个变量,它存储了内存地址,通过这个地址可以访问内存中的数据。Java中的引用变量与C或C++中的指针有一些相似之处,但也有重要的区别。例如,Java中的引用变量是类型安全的,而C或C++中的指针需要显式地进行类型转换。此外,Java中的引用变量是由垃圾回收器管理的,而C或C++中的指针需要手动管理内存。
总之,Java中的引用变量与C或C++中的指针有一些相似之处,但也有重要的区别。Java中的引用变量是类型安全的,由垃圾回收器管理,而C或C++中的指针需要显式地进行类型转换和手动管理内存。
补充说明
逃逸分析 (Escape Analysis)
- 定义:逃逸分析是一种编译器优化技术,它分析对象的作用域,判断一个对象是否“逃逸”出了当前方法的作用域,也就是说,判断该对象是否可能被其他方法访问。
- 目的:如果一个对象仅在一个方法内部使用,并且不会被其他任何地方引用,那么它可以被视为不会“逃逸”出去。
- 优化:对于不逃逸的对象,JVM 可以选择不在堆上为它们分配内存,而是直接在栈上分配。这样做的好处是可以避免垃圾回收的开销,并提高程序的性能。
栈上分配 (Stack Allocation)
- 概念:栈上分配是指将原本应该在堆上分配的对象直接放在栈上的一种优化方式。
- 适用场景:
- 当一个对象的生命周期很短,且只在局部范围内使用。
- 对象大小较小,适合放在栈上。
- 优点:
- 减少了垃圾回收的压力。
- 加快了对象的创建和销毁速度。
- 减少了内存碎片问题。
堆碎片化 (Heap Fragmentation)
- 定义:随着对象的创建和销毁,堆内存中可能会出现许多不连续的小块空闲内存,这使得大块内存分配变得困难。
- 影响:碎片化会导致内存分配效率降低,甚至导致内存不足的情况发生。
- 解决方法:
- 压缩式垃圾回收:某些垃圾回收器会在清理内存的同时重新整理内存中的对象,减少碎片。
- 分代收集:JVM 通常会将堆分为新生代和老年代,新生代中使用复制算法来减少碎片。