• 认识线程栈与托管堆


        
        
        
    一、什么叫堆?什么叫栈?

        1、堆(Heap)和栈(Stack)是两种不同的内存分配方式。
            堆是块动态分配的内存区域,用于存储引用类型的对象。在堆中分配的内存需要手动释放,否则会造成内存泄漏。堆的分配和释放是由垃圾回收器(Garbage Collector)负责管理的。

            栈是一种自动分配的内存区域,用于存储值类型的变量和方法调用的上下文信息。栈的分配和释放是由编译器自动完成的,无需手动管理。栈上的变量的生命周期与其所在的作用域相对应,当作用域结束时,栈上的变量会被自动释放。

            总结来说,堆用于动态分配对象和数据结构,而栈用于管理方法调用和局部变量。
        
        
        2、为什么要把内存分成堆与栈
            将内存分成堆和栈是为了提高内存分配和使用的效率和灵活性。通过合理地选择在堆或栈上分配内存,我们可以更好地管理和优化程序的内存消耗,并确保内存的分配和释放符合程序的需求和逻辑。
            
            要将内存分成堆与栈的几个原因:

            (1)内存分配的效率: 堆和栈使用不同的分配机制,适用于不同类型的内存对象。栈上的内存分配是通过移动栈指针来实现的,速度非常快,并且分配和释放内存的成本相对较低。而堆上的内存分配需要更复杂的管理机制,包括垃圾回收和对象定位等,因此相对较慢。所以,对于一些生命周期较短的对象,可以选择在栈上分配,而对于生命周期较长的对象,可以选择在堆上分配。

            (2)内存管理的灵活性: 堆内存的分配和释放可以发生在任何时候,具有更大的灵活性。可以通过手动分配和释放内存(例如 new 和 delete 操作符),也可以由垃圾回收器自动管理。而栈上的内存分配和释放是由编译器自动处理的,无需人工干预。

            (3)内存的生命周期: 栈上的内存分配是基于“后进先出”(LIFO)的原则,当一个方法或函数调用结束时,栈上分配的内存会立即自动释放。而在堆上分配的内存,其生命周期可以长至程序运行的整个时间,需要手动释放或由垃圾回收器管理。

            (4)内存的大小限制: 栈内存的大小是固定的,并由编译器在编译时确定。而堆内存的大小可以动态增长和收缩,只要操作系统允许,并且有足够的可用内存。
        
        
        3、为什么栈底在高地址,栈顶在低地址?堆则相反。
            
            栈和堆的地址分布是由它们的功能和实现方式决定的。栈是一种固定大小的数据结构,用于存储函数调用信息,所以栈的分配方式是从高地址向低地址分配。堆是一种动态分配的内存区域,用于存储对象,所以堆的分配方式是从低地址向高地址分配。
            
            栈是一种具有固定大小的数据结构,用于存储局部变量、函数调用信息和返回地址等数据。栈的大小在编译时就确定下来了,每个函数调用都会在栈上分配一块空间用于存储局部变量等数据。由于栈是一种后进先出(LIFO)的数据结构,所以栈的分配方式是从高地址向低地址分配,也就是栈底位于高地址,栈顶位于低地址。

            堆是一种动态分配的内存区域,用于存储动态分配的对象。堆的大小在程序运行时动态增长或缩小,由垃圾回收器负责管理。由于堆的分配方式是根据对象的大小和分配顺序进行的,所以堆的分配方式是从低地址向高地址分配,也就是堆底位于低地址,堆顶位于高地址。
            
            栈和堆的结构决定了它们的大小限制和分配方式。栈是有限大小的,而堆在理论上是无限大小的,但实际上受制于系统资源的限制。
            
            由于栈的大小是有限的,当栈空间被使用完时,就会发生栈溢出的错误。
            
            由于堆的大小是动态的,所以它在理论上是无限大小的,但实际上受制于操作系统和硬件的限制。
            
            

     
    二、线程栈与托管堆


        1、什么是线程栈?
            
            线程栈是用于存储线程执行过程中局部变量、方法调用信息和返回地址等数据的一块内存区域。每个线程都有自己的线程栈,用于存储线程的执行上下文信息。

            线程栈的大小是固定的,一般为1MB左右。当线程调用方法时,会在线程栈上为该方法分配一块栈帧,用于存储方法的局部变量、方法参数和返回地址等信息。当方法调用结束后,对应的栈帧会被销毁,线程栈会回到方法调用前的状态。

            线程栈的大小限制了线程可以调用的方法的嵌套深度。如果方法调用层次过深,线程栈可能会溢出。在C#中,可以使用Thread类的构造函数指定线程栈的大小,以避免线程栈溢出的问题。        
        
        
        2、什么是托管堆?
            
            托管堆(Managed Heap)是在托管环境中用于存储动态分配的对象的一块内存区域。在C#和其他托管语言中,对象的内存分配和释放由垃圾回收器(Garbage Collector)来管理。

            托管堆是一个大的连续内存块,用于存储各种类型的对象。当我们在代码中创建一个对象时,垃圾回收器会负责在托管堆上分配一块内存来存储该对象的数据。对象的大小可以是不同的,但它们都存储在托管堆上。

            托管堆的优点是它提供了自动的内存管理。当对象不再被引用时,垃圾回收器会自动回收这些对象所占用的内存空间,并释放给其他对象使用。这减轻了开发人员的负担,不需要手动管理内存的分配和释放。

            托管堆还提供了对象的内存分配和释放的高效性。由于对象的内存是连续分配的,可以通过简单的指针操作来访问和操作对象的数据。而且,垃圾回收器会定期进行垃圾回收,对不再使用的对象进行清理,使得托管堆的内存利用率更高。

            注意,托管堆只存储对象的数据,而对象的方法代码是存储在托管代码区域(Managed Code Area)中的。
        
        
        3、为什么托管堆是基于进程,线程栈是基于线程的
            
            托管堆和线程栈在内存管理和使用方式上有不同的特点和目的。
            托管堆用于存储动态分配的对象,提供自动的内存管理和高效的内存分配。
            线程栈用于存储线程的执行上下文信息,提供了局部变量的存储和方法调用的支持。
            它们在不同的层次上为应用程序提供了内存管理和执行环境的支持。
        
            托管堆是基于进程的,因为它是在进程的虚拟地址空间中分配的一块内存区域。虚拟地址空间是由操作系统分配给进程的一块内存,用于存储进程的代码、数据和堆栈等信息。在这个虚拟地址空间中,托管堆是一个大的连续内存块,用于存储动态分配的对象。

            线程栈是基于线程的,因为每个线程都有自己独立的线程栈。线程栈是在线程的虚拟地址空间中分配的一块内存区域,用于存储线程执行过程中的局部变量、方法调用信息和返回地址等数据。每个线程都有自己独立的线程栈,用于存储线程的执行上下文信息。
        
        
        4、如果一个线程栈使用超过1M,将会怎样?
        
            如果一个线程栈使用超过1M,可能会导致栈溢出(stack overflow)的错误。

            线程栈的大小在创建线程时就确定了,通常默认情况下是1M。线程栈用于存储线程的执行上下文信息、局部变量和方法调用信息等。当一个线程的栈空间被使用完毕时,就会发生栈溢出的错误。

            栈溢出错误通常是由于递归调用或者过多的局部变量占用栈空间导致的。当栈空间被使用完毕时,操作系统会抛出栈溢出异常,导致程序终止。

            为了避免栈溢出错误,可以考虑以下几种方法:
                减少递归调用的深度或次数,避免无限递归。
                减少局部变量的使用,尽量使用全局变量或静态变量。
                调整线程栈的大小,增加栈空间的大小。
                使用堆来存储大量数据,而不是使用栈。
        
        
        5、一个程序内存分配、使用流程是怎么样的?
            
            一个程序在使用内存时,会分配不同的内存区域来存储不同类型的数据和代码。一般情况下的内存分配和使用流程:

            (1)代码区(Code Segment):
            用于存储程序的指令代码,通常是只读的。在程序加载到内存时,代码区会被分配一块内存空间,并将程序的指令代码加载到该区域。

            (2)静态区(Static Segment):
            也称为全局区或数据段,用于存储全局变量和静态变量。在程序加载到内存时,静态区会被分配一块内存空间,并将全局变量和静态变量初始化并存储在该区域。

            (3)堆(Heap):
            用于存储动态分配的对象和数据。在程序运行时,可以通过动态内存分配函数(如malloc、new等)从堆中分配一块内存空间来存储对象和数据。堆的大小在程序运行时动态增长或缩小,由垃圾回收器负责管理。

            (4)栈(Stack):
            用于存储函数调用信息、局部变量和临时数据。每个线程都有自己的栈空间,用于存储当前线程的执行上下文信息。在函数调用时,会在栈上分配一块固定大小的空间来存储局部变量和临时数据。栈的大小在编译时就确定下来了。

            (5)托管堆(Managed Heap):
            在托管语言(如C#、Java)中,还有一种特殊的堆称为托管堆。这是由托管运行时环境(CLR、JVM)管理的堆,用于存储托管对象。托管堆的分配和释放由垃圾回收器自动进行,开发人员不需要手动管理内存。

            整个内存分配和使用流程大致如下:
            (1)程序加载到内存时,代码区被分配一块内存空间,并将指令代码加载到该区域。
            (2)静态区被分配一块内存空间,并将全局变量和静态变量初始化并存储在该区域。
            (3)在程序运行时,可以通过动态内存分配函数从堆中分配一块内存空间来存储动态分配的对象和数据。
            (4)每个线程都有自己的栈空间,用于存储函数调用信息、局部变量和临时数据。
            (5)在托管语言中,还有托管堆用于存储托管对象,其分配和释放由垃圾回收器自动进行。
            
            可以参考动画演示:https://www.bilibili.com/video/BV1VZ4y1N7WK/?spm_id_from=333.880.my_history.page.click&vd_source=2a0404a7c8f40ef37a32eed32030aa18
        
        

  • 相关阅读:
    如何通过QGC实现无人机的自动化任务,包括飞行计划的制定和执行。
    乔拓云模板助力,微信小程序快速上线无需愁备案
    QLayout布局间消除间隙和QLayout边缘空白调整
    C# OpenVINO Cls 图像分类
    Azure DevOps (十) 通过流水线完成Docker镜像的部署
    Java代码hello word
    LDR6035智能蓝牙音响可充可放(5.9.12.15.20V)快充快放设备充电
    DGIOT基本功能介绍——组态编辑配置
    为什么CSS中的calc函数可能会不生效?
    CLion和WSL配置MPI运行及调试环境
  • 原文地址:https://blog.csdn.net/dzweather/article/details/132891093