内存分配策略中,堆和栈的区别

根据编译原理,程序在运行时的内存分配策略有三种:

1.静态 Static

指在编译时就能确定的每个数据目标在运行时刻需要的存储空间需求。因而在编译的时候就可以给他们分配固定的存储空间。 这种数据目标在编译时就为他们分配固定的内存。

限制:

  • 代码中不能有可变数据结构,如数组。
  • 代码中不允许有递归或嵌套结构的出现。
public class EaseConstant {
    public static final String MESSAGE_ATTR_IS_VOICE_CALL = "is_voice_call";
    public static final String MESSAGE_ATTR_IS_VIDEO_CALL = "is_video_call";
   
    public static final String MESSAGE_ATTR_IS_BIG_EXPRESSION = "em_is_big_expression";
    public static final String MESSAGE_ATTR_EXPRESSION_ID = "em_expression_id";
   
    public static final String MESSAGE_ATTR_AT_MSG = "em_at_list";
    public static final String MESSAGE_ATTR_VALUE_AT_MSG_ALL = "ALL";

    public static final int CHATTYPE_SINGLE = 1;
    public static final int CHATTYPE_GROUP = 2;
    public static final int CHATTYPE_CHATROOM = 3;
    
    public static final String EXTRA_CHAT_TYPE = "chatType";
    public static final String EXTRA_USER_ID = "userId";
}

2.栈式 Stack

栈式存储分配可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的,和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能知道。

指在编译时不能确定大小,但在运行的时候能够确定,且规定在运行中进入一个程序模块时,就必须知道该模块所需要的数据区大小,才能为其分配内存,和我们在数据结构中所知道的栈一样,内存分配为e栈原则,先进后出的原则进行分配。

分配是在运行时执行的,但是大小是在编译时确定的;

特点:
在C/C++中,所有的方法调用都是通过栈来进行的,所有局部变量,形式参数都是从栈中分配内存空间的。

栈的分配和回收:

栈分配内存空间:从栈低向栈顶,依次存储;
栈回收内存空间:修改栈顶指针的位置,完成栈中内容销毁,这样的模式速度很快。

栈 :存放基本数据类型,速度快

  • 栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄;
  • 栈的存取速度比堆要快;
  • 栈数据可以共享;
  • 栈的数据大小与生存期必须是确定的,缺乏灵活性。

3.堆式 Heap

指编译时,运行时模块入口都不能确定存储要求的数据结构的内存分配。
比如可变长度的串和对象实例。
堆由大片的可利用的块或空闲组成,堆中的内存可以按照任意顺序分配和释放。

堆是在运行的时候,请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁的时候都要占用时间,因此对的效率低下。

堆的优点:编译时不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长时间,因此堆存储数据时,灵活性比较大;

在面向对象编程中,堆是必不可少的,因为面向对象的多态性,多态变量所需的存储空间只有在运行时创建了对象之后才能确定。

堆: 用new建立,垃圾自动回收负责回收

  • 堆是一个"运行时"数据区,类实例化的对象就是从堆上去分配空间的;
  • 在堆上分配空间是通过"new"等指令建立的;
  • Java针对堆的操作和C++的区别就是,Java不需要在空间不用的时候来显式的释放;
  • Java的堆是由Java的垃圾回收机制来负责处理的,堆是动态分配内存大小,垃圾收集器可以自动回收不再使用的内存空间。
  • 但缺点是,因为在运行时动态分配内存,所以内存的存取速度较慢。

4. 堆和栈的比较

比较方面
功能的比较 存放对象 执行程序
存储内容 new关键字创建的内容 局部变量,形式参数
存储速度 快(所以用来执行程序)

5.JVM中的堆栈

JVM是基于堆栈的虚拟机,JVM中的堆栈是两块不同的存储区域。

JVM为每个线程程都分配了一个堆和栈,所以对于java程序来说,程序的运行是通过对堆栈的操作来完成的。

JVM 堆 JVM 栈
是存储的单位 是运行时的单位
JVM堆解决的是数据存储的问题,即数据怎么放、放在哪儿。 JVM栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;
JVM堆中存的是对象。 JVM栈中存的是基本数据类型和JVM堆中对象的引用

一个对象的大小是不可估计的,或者说是可以动态变化的,但是在JVM栈中,一个对象只对应了一个4btye的引用(JVM堆JVM栈分离的好处:))。

为什么不把基本类型放JVM堆中呢?
因为基本类型其占用的空间一般是1~8个字节(需要空间比较少),而且因为是基本类型,所以不会出现动态增长的情况(长度固定),因此JVM栈中存储就够了,如果把他存在JVM堆中是没有什么意义的(还会浪费空间,后面说明)。可以这么说,基本类型和对象的引用都是存放在JVM栈中,而且都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。

但是基本类型、对象引用和对象本身就有所区别了,因为一个是JVM栈中的数据一个是JVM堆中的数据的引用。最常见的一个问题就是,Java中参数传递时的问题。

JAVA中堆栈的应用

Java中的参数传递时传值呢?还是传引用?

要说明这个问题,先要明确两点:
1.不要试图与C进行类比,Java中没有指针的概念
2.程序运行永远都是在JVM栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题,不会直接传对象本身。

总结:传递的是对象的引用值或是基本类型的值;

明确以上两点后。Java在方法调用传递参数时,因为没有指针,所以它都是进行传值调用(这点可以参考C的传值调用)。因此,很多书里面都说Java是进行传值调用,这点没有问题,而且也简化的C中复杂性。

但是传引用的错觉是如何造成的呢?
在运行JVM栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到JVM堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是JVM堆中的数据。所以这个修改是可以保持的了。

对象,从某种意义上说,是由基本类型组成的。可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。

JVM堆和JVM栈中,JVM栈是程序运行最根本的东西。程序运行可以没有JVM堆,但是不能没有JVM栈。而JVM堆是为JVM栈进行数据存储服务,说白了JVM堆就是一块共享的内存。不过,正是因为JVM堆和JVM栈的分离的思想,才使得Java的垃圾回收成为可能。

JVM中线程堆栈
线程(thread),有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。  线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程。

1、线程与进程
线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文.多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定. 线程的运行中需要使用计算机的内存资源和CPU

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。

推荐阅读更多精彩内容