超干货!彻底搞懂Golang内存管理和垃圾回收

标签: dev | 发表时间:2022-09-23 00:00 | 作者:
出处:https://itindex.net/relian

导语 | 现代高级编程语言管理内存的方式分自动和手动两种。手动管理内存的典型代表是C和C++,编写代码过程中需要主动申请或者释放内存;而Java和Go等语言使用自动的内存管理系统,由内存分配器和垃圾收集器来代为分配和回收内存,开发者只需关注业务代码而无需关注底层内存分配和回收,虽然语言帮我们处理了这部分,但是还是有必要去了解一下底层的架构设计和执行逻辑,这样可以更好的掌握一门语言,本文主要以go内存管理为切入点再到go垃圾回收,系统地讲解了go自动内存管理系统的设计和原理。


一、TCMalloc

go内存管理是借鉴了TCMalloc的设计思想,TCMalloc全称Thead-Caching Malloc,是google开发的内存分配器,为了方便理解下面的go内存管理,有必要要先熟悉一下TCMalloc。



(一)Page


操作系统对内存管理以页为单位,TCMalloc也是这样,只不过TCMalloc里的Page大小与操作系统里的大小并不一定相等,而是倍数关系。



(二) Span


一组连续的Page被称为Span,比如可以有4个页大小的Span,也可以有8个页大小的Span,Span比Page高一个层级,是为了方便管理一定大小的内存区域,Span是TCMalloc中内存管理的基本单位。



(三) ThreadCache


每个线程各自的Cache,一个Cache包含多个空闲内存块链表,每个链表连接的都是内存块,同一个链表上内存块的大小是相同的,也可以说按内存块大小,给内存块分了个类,这样可以根据申请的内存大小,快速从合适的链表选择空闲内存块。由于每个线程有自己的ThreadCache,所以ThreadCache访问是无锁的。



(四)CentralCache


是所有线程共享的缓存,也是保存的空闲内存块链表,链表的数量与ThreadCache中链表数量相同,当ThreadCache内存块不足时,可以从CentralCache取,当ThreadCache内存块多时,可以放回CentralCache。由于CentralCache是共享的,所以它的访问是要加锁的。



(五)PageHeap


PageHeap是堆内存的抽象,PageHeap存的也是若干链表,链表保存的是Span,当CentralCache没有内存的时,会从PageHeap取,把1个Span拆成若干内存块,添加到对应大小的链表中,当CentralCache内存多的时候,会放回PageHeap。



(六)TCMalloc对象分配


小对象直接从ThreadCache分配,若ThreadCache不够则从CentralCache中获取内存,CentralCache内存不够时会再从PageHeap获取内存,大对象在PageHeap中选择合适的页组成span用于存储数据。



二、GO内存管理


经过上一节对TCMalloc内存管理的描述,对接下来理解go的内存管理会有大致架构的熟悉,go内存管理架构取之TCMalloc不过在细节上有些出入,先来看一张go内存管理的架构图:



(一)Page


和TCMalloc中page相同,上图中最下方浅蓝色长方形代表一个page。



(二)Span


与TCMalloc中的Span相同,Span是go内存管理的基本单位,代码中为mspan,一组连续的Page组成1个Span,所以上图一组连续的浅蓝色长方形代表的是一组Page组成的1个Span,另外,1个淡紫色长方形为1个Span。



(三)mcache


mcache与TCMalloc中的ThreadCache类似,mcache保存的是各种大小的Span,并按Span class分类,小对象直接从mcache分配内存,它起到了缓存的作用,并且可以无锁访问。但mcache与ThreadCache也有不同点,TCMalloc中是每个线程1个ThreadCache,Go中是每个P拥有1个mcach,因为在Go程序中,当前最多有GOMAXPROCS个线程在运行,所以最多需要GOMAXPROCS个mcache就可以保证各线程对mcache的无锁访问,下图是G,P,M三者之间的关系:



(四)mcentral


mcentral与TCMalloc中的CentralCache类似,是所有线程共享的缓存,需要加锁访问,它按Span class对Span分类,串联成链表,当mcache的某个级别Span的内存被分配光时,它会向mcentral申请1个当前级别的Span。但mcentral与CentralCache也有不同点,CentralCache是每个级别的Span有1个链表,mcache是每个级别的Span有2个链表。



(五)mheap


mheap与TCMalloc中的PageHeap类似,它是堆内存的抽象,把从OS(系统)申请出的内存页组织成Span,并保存起来。当mcentral的Span不够用时会向mheap申请,mheap的Span不够用时会向OS申请,向OS的内存申请是按页来的,然后把申请来的内存页生成Span组织起来,同样也是需要加锁访问的。但mheap与PageHeap也有不同点:mheap把Span组织成了树结构,而不是链表,并且还是2棵树,然后把Span分配到heapArena进行管理,它包含地址映射和span是否包含指针等位图,这样做的主要原因是为了更高效的利用内存:分配、回收和再利用。



(六)内存分配


Go中的内存分类并不像TCMalloc那样分成小、中、大对象,但是它的小对象里又细分了一个Tiny对象,Tiny对象指大小在1Byte到16Byte之间并且不包含指针的对象。小对象和大对象只用大小划定,无其他区分,其中小对象大小在16Byte到32KB之间,大对象大小大于32KB。span规格分类 上面说到go的内存管理基本单位是span,且span有不同的规格,要想区分出不同的的span,我们必须要有一个标识,每个span通过spanclass标识属于哪种规格的span,golang的span规格一共有67种,具体如下:


   //from runtime.gosizeclasses.go       
// class bytes/obj bytes/span objects tail waste max waste // 1 8 8192 1024 0 87.50% // 2 16 8192 512 0 43.75% // 3 32 8192 256 0 46.88% // 4 48 8192 170 32 31.52% // 5 64 8192 128 0 23.44% // 6 80 8192 102 32 19.07% // 7 96 8192 85 32 15.95% // 8 112 8192 73 16 13.56% // 9 128 8192 64 0 11.72% // 10 144 8192 56 128 11.82% // 11 160 8192 51 32 9.73% // 12 176 8192 46 96 9.59% // 13 192 8192 42 128 9.25% // 14 208 8192 39 80 8.12% // 15 224 8192 36 128 8.15% // 16 240 8192 34 32 6.62% // 17 256 8192 32 0 5.86% // 18 288 8192 28 128 12.16% // 19 320 8192 25 192 11.80% // 20 352 8192 23 96 9.88% // 21 384 8192 21 128 9.51% // 22 416 8192 19 288 10.71% // 23 448 8192 18 128 8.37% // 24 480 8192 17 32 6.82% // 25 512 8192 16 0 6.05% // 26 576 8192 14 128 12.33% // 27 640 8192 12 512 15.48% // 28 704 8192 11 448 13.93% // 29 768 8192 10 512 13.94% // 30 896 8192 9 128 15.52% // 31 1024 8192 8 0 12.40% // 32 1152 8192 7 128 12.41% // 33 1280 8192 6 512 15.55% // 34 1408 16384 11 896 14.00% // 35 1536 8192 5 512 14.00% // 36 1792 16384 9 256 15.57% // 37 2048 8192 4 0 12.45% // 38 2304 16384 7 256 12.46% // 39 2688 8192 3 128 15.59% // 40 3072 24576 8 0 12.47% // 41 3200 16384 5 384 6.22% // 42 3456 24576 7 384 8.83% // 43 4096 8192 2 0 15.60% // 44 4864 24576 5 256 16.65% // 45 5376 16384 3 256 10.92% // 46 6144 24576 4 0 12.48% // 47 6528 32768 5 128 6.23% // 48 6784 40960 6 256 4.36% // 49 6912 49152 7 768 3.37% // 50 8192 8192 1 0 15.61% // 51 9472 57344 6 512 14.28% // 52 9728 49152 5 512 3.64% // 53 10240 40960 4 0 4.99% // 54 10880 32768 3 128 6.24% // 55 12288 24576 2 0 11.45% // 56 13568 40960 3 256 9.99% // 57 14336 57344 4 0 5.35% // 58 16384 16384 1 0 12.49% // 59 18432 73728 4 0 11.11% // 60 19072 57344 3 128 3.57% // 61 20480 40960 2 0 6.87% // 62 21760 65536 3 256 6.25% // 63 24576 24576 1 0 11.45% // 64 27264 81920 3 128 10.00% // 65 28672 57344 2 0 4.91% // 66 32768 32768 1 0 12.50%


由上表可见最大的对象是32KB大小,超过32KB大小的由特殊的class表示,该class ID为0,每个class只包含一个对象。所以上面只有列出了1-66。内存大小转换,下面还要三个数组,分别是:class_to_size,size_to_class和class_to_allocnpages3个数组,对应下图上的3个箭头:



以第一列为例,类别1的对象大小是8bytes,所以class_to_size[1]=8;span大小是8KB,为1页,所以class_to_allocnpages[1]=1,下图是go源码中大小转换数组。



为对象寻找span,寻找span的流程如下:


  • 计算对象所需内存大小size。


  • 根据size到size class映射,计算出所需的size class。


  • 根据size class和对象是否包含指针计算出span class。


  • 获取该span class指向的span。


以分配一个包含指针大小为20Byte的对象为例,根据映射表:


   // class  bytes/obj  bytes/span  objects  tail waste  max waste   //     1          8        8192     1024           0     87.50%   //     2         16        8192      512           0     43.75%   //     3         32        8192      256           0     46.88%


size class 3,它的对象大小范围是(16,32]Byte,20Byte刚好在此区间,所以此对象的size class为3,Size class到span class的计算如下:


   // noscan为false代表对象包含指针   funcmakeSpanClass(sizeclassuint8, noscanbool)spanClass{   returnspanClass(sizeclass<<1) | spanClass(bool2int(noscan))   }


所以,对应的span class为:


   spanclass=3<<1|0=6


所以该对象需要的是span class 7指向的span,自此,小对象内存分配完成。


   //from runtime.gomalloc.go       
var sizeclass uint8 //step1: 确定规格sizeClass if size <= smallSizeMax-8 { sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)] }else{ sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)] } size = uintptr(class_to_size[sizeclass]) // size class到span class spc := makeSpanClass(sizeclass, noscan) //step2: 分配对应spanClass 的 span span = c.alloc[spc] v := nextFreeFast(span) if v == 0 { v, span, shouldhelpgc = c.nextFree(spc) } x = unsafe.Pointer(v) if needzero &amp;&amp; span.needzero != 0 { memclrNoHeapPointers(unsafe.Pointer(v), size) }


大对象(>32KB)的分配则简单多了,直接在mheap上进行分配,首先计算出需要的内存页数和span class级别,然后优先从free中搜索可用的span,如果没有找到,会从scav中搜索可用的span,如果还没有找到,则向OS申请内存,再重新搜索2棵树,必然能找到span。如果找到的span比需求的span大,则把span进行分割成2个span,其中1个刚好是需求大小,把剩下的span再加入到free中去。



三、垃圾回收


(一)标记-清除


标记-清除算法是第一种自动内存管理,基于追踪的垃圾收集算法。算法思想在70年代就提出了,是一种非常古老的算法。内存单元并不会在变成垃圾立刻回收,而是保持不可达状态,直到到达某个阈值或者固定时间长度。这个时候系统会挂起用户程序,也就是STW,转而执行垃圾回收程序。垃圾回收程序对所有的存活单元进行一次全局遍历确定哪些单元可以回收。算法分两个部分:标记(mark)和清除(sweep)。标记阶段表明所有的存活单元,清扫阶段将垃圾单元回收。可视化可以参考下图。



标记-清除算法的优点也就是基于追踪的垃圾回收算法具有的优点:避免了引用计数算法的缺点(不能处理循环引用,需要维护指针)。缺点也很明显,需要STW。



(二)三色可达性分析


三色标记算法是对标记阶段的改进,原理如下:


  • 起初所有对象都是白色。


  • 从根出发扫描所有可达对象,标记为灰色,放入待处理队列。


  • 从队列取出灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。


  • 重复上一步,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。



三色标记的一个明显好处是能够让用户程序和mark并发的进行,不过三色标记清除算法本身是不可以并发或者增量执行的,它需要STW,而如果并发执行,用户程序可能在标记执行的过程中修改对象的指针,导致可能将本该死亡的对象标记为存活和本该存活的对象标记为死亡,为了解决这种问题,go v1.8之后使用混合写屏障技术支持并发和增量执行,将垃圾收集的时间缩短至0.5ms以内。



(三)gc触发


在堆上分配大于32K byte对象的时候进行检测此时是否满足垃圾回收条件,如果满足则进行垃圾回收


   funcmallocgc(sizeuintptr, typ *_type, needzerobool)unsafe.Pointer{   ...   shouldhelpgc :=false   // 分配的对象小于 32K byte   ifsize <= maxSmallSize {   ...   }else{   shouldhelpgc =true   ...   }   ...   // gcShouldStart() 函数进行触发条件检测   ifshouldhelpgc && gcShouldStart(false) {   // gcStart() 函数进行垃圾回收   gcStart(gcBackgroundMode,false)   }   }


上面是自动垃圾回收,还有一种主动垃圾回收,通过调用runtime.GC(),这是阻塞式的。


   // GC runs a garbage collection and blocks the caller until the   // garbage collection is complete. It may also block the entire   // program.   funcGC(){   gcStart(gcForceBlockMode,false)   }


系统gc触发条件:触发条件主要关注下面代码中的中间部分:forceTrigger||memstats.heap_live>=memstats.gc_trigger。forceTrigger是forceGC的标志,后面半句的意思是当前堆上的活跃对象大于我们初始化时候设置的GC触发阈值,在malloc以及free的时候heap_live会一直进行更新。


   // gcShouldStart returns true if the exit condition for the _GCoff   // phase has been met. The exit condition should be tested when   // allocating.   //   // If forceTrigger is true, it ignores the current heap size, but   // checks all other conditions. In general this should be false.   funcgcShouldStart(forceTriggerbool)bool{   returngcphase == _GCoff && (forceTrigger || memstats.heap_live >= memstats.gc_trigger) && memstats.enablegc && panicking ==0&& gcpercent >=0   }       
//初始化的时候设置 GC 的触发阈值 funcgcinit(){ _ = setGCPercent(readgogc()) memstats.gc_trigger = heapminimum ... } // 启动的时候通过 GOGC 传递百分比 x // 触发阈值等于 x * defaultHeapMinimum (defaultHeapMinimum 默认是 4M) funcreadgogc()int32{ p := gogetenv("GOGC") ifp =="off"{ return-1 } ifn, ok := atoi32(p); ok { returnn } return100 }



(四)gc过程


下列源码是基于go 1.8,由于源码过长,所以这里尽量只关注主流程


  • gcStart


   // gcStart 是 GC 的入口函数,根据 gcMode 做处理。   // 1. gcMode == gcBackgroundMode(后台运行,也就是并行), _GCoff -> _GCmark   // 2. 否则 GCoff -> _GCmarktermination,这个时候就是主动 GC   funcgcStart(mode gcMode, forceTriggerbool){   ...   //在后台启动 mark worker   ifmode == gcBackgroundMode {   gcBgMarkStartWorkers()   }   ...   // Stop The World   systemstack(stopTheWorldWithSema)   ...   ifmode == gcBackgroundMode {   // GC 开始前的准备工作       
//处理设置 GCPhase,setGCPhase 还会 开始写屏障 setGCPhase(_GCmark)
gcBgMarkPrepare()// Must happen before assist enable. gcMarkRootPrepare()
// Mark all active tinyalloc blocks. Since we're // allocating from these, they need to be black like // other allocations. The alternative is to blacken // the tiny block on every allocation from it, which // would slow down the tiny allocator. gcMarkTinyAllocs()
// Start The World systemstack(startTheWorldWithSema) }else{ ... } }


  • Mark


   funcgcStart(mode gcMode, forceTriggerbool){   ...   //在后台启动 mark worker   ifmode == gcBackgroundMode {   gcBgMarkStartWorkers()   }   }       
funcgcBgMarkStartWorkers(){ // Background marking is performed by per-P G's. Ensure that // each P has a background GC G. for_, p :=range&allp { ifp ==nil|| p.status == _Pdead { break } ifp.gcBgMarkWorker ==0{ gogcBgMarkWorker(p) notetsleepg(&work.bgMarkReady,-1) noteclear(&work.bgMarkReady) } } } // gcBgMarkWorker 是一直在后台运行的,大部分时候是休眠状态,通过 gcController 来调度 funcgcBgMarkWorker(_p_ *p){ for{ // 将当前 goroutine 休眠,直到满足某些条件 gopark(...) ... // mark 过程 systemstack(func(){ // Mark our goroutine preemptible so its stack // can be scanned. This lets two mark workers // scan each other (otherwise, they would // deadlock). We must not modify anything on // the G stack. However, stack shrinking is // disabled for mark workers, so it is safe to // read from the G stack. casgstatus(gp, _Grunning, _Gwaiting) switch_p_.gcMarkWorkerMode { default: throw("gcBgMarkWorker: unexpected gcMarkWorkerMode") casegcMarkWorkerDedicatedMode: gcDrain(&_p_.gcw, gcDrainNoBlock|gcDrainFlushBgCredit) casegcMarkWorkerFractionalMode: gcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit) casegcMarkWorkerIdleMode: gcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit) } casgstatus(gp, _Gwaiting, _Grunning) }) ... } }


Mark阶段的标记代码主要在函数gcDrain()中实现


   // gcDrain scans roots and objects in work buffers, blackening grey   // objects until all roots and work buffers have been drained.   funcgcDrain(gcw *gcWork, flags gcDrainFlags){   ...   // Drain root marking jobs.   ifwork.markrootNext < work.markrootJobs {   for!(preemptible && gp.preempt) {   job := atomic.Xadd(&work.markrootNext, +1) -1   ifjob >= work.markrootJobs {   break   }   markroot(gcw, job)   ifidle && pollWork() {   gotodone   }   }   }       
// 处理 heap 标记 // Drain heap marking jobs. for!(preemptible && gp.preempt) { ... //从灰色列队中取出对象 varbuintptr ifblocking { b = gcw.get() }else{ b = gcw.tryGetFast() ifb ==0{ b = gcw.tryGet() } } ifb ==0{ // work barrier reached or tryGet failed. break } //扫描灰色对象的引用对象,标记为灰色,入灰色队列 scanobject(b, gcw) } }


  • Sweep


   funcgcSweep(mode gcMode){   ...   //阻塞式   if!_ConcurrentSweep || mode == gcForceBlockMode {   // Special case synchronous sweep.   ...   // Sweep all spans eagerly.   forsweepone() != ^uintptr(0) {   sweep.npausesweep++   }   // Do an additional mProf_GC, because all 'free' events are now real as well.   mProf_GC()   mProf_GC()   return   }       
// 并行式 // Background sweep. lock(&sweep.lock) ifsweep.parked { sweep.parked =false ready(sweep.g,0,true) } unlock(&sweep.lock) }


对于并行式清扫,在GC初始化的时候就会启动 bgsweep(),然后在后台一直循环


   funcbgsweep(cchanint){   sweep.g = getg()       
lock(&sweep.lock) sweep.parked =true c <-1 goparkunlock(&sweep.lock,"GC sweep wait", traceEvGoBlock,1)
for{ forgosweepone() != ^uintptr(0) { sweep.nbgsweep++ Gosched() } lock(&sweep.lock) if!gosweepdone() { // This can happen if a GC runs between // gosweepone returning ^0 above // and the lock being acquired. unlock(&sweep.lock) continue } sweep.parked =true goparkunlock(&sweep.lock,"GC sweep wait", traceEvGoBlock,1) } }
funcgosweepone()uintptr{ varretuintptr systemstack(func(){ ret = sweepone() }) returnret }


不管是阻塞式还是并行式,最终都会调用sweepone()。上面说过go内存管理都是基于span的,mheap_是一个全局的变量,所有分配的对象都会记录在mheap_中。在标记的时候,我们只要找到对对象对应的span进行标记,清扫的时候扫描span,没有标记的span就可以回收了。


   // sweeps one span   // returns number of pages returned to heap, or ^uintptr(0) if there is nothing to sweep   funcsweepone()uintptr{   ...   for{   s := mheap_.sweepSpans[1-sg/2%2].pop()   ...   if!s.sweep(false) {   // Span is still in-use, so this returned no   // pages to the heap and the span needs to   // move to the swept in-use list.   npages =0   }   }   }       
// Sweep frees or collects finalizers for blocks not marked in the mark phase. // It clears the mark bits in preparation for the next GC round. // Returns true if the span was returned to heap. // If preserve=true, don't return it to heap nor relink in MCentral lists; // caller takes care of it. func(s *mspan)sweep(preservebool)bool{ ... }


参考资料:

1.《go语言设计与实现》


 作者简介


冷易

腾讯后台开发工程师

腾讯后台开发工程师,目前负责腾讯医药平台后端开发工作,精通java、go底层设计架构,有丰富的高并发性能优化,分布式系统开发经验。



推荐阅读


福利
我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复  ebook 获取;还可以回复「 进群」,和数万 Gopher 交流学习。

相关 [干货 golang 内存管理] 推荐:

超干货!彻底搞懂Golang内存管理和垃圾回收

- - IT瘾-dev
导语 | 现代高级编程语言管理内存的方式分自动和手动两种. go内存管理是借鉴了TCMalloc的设计思想,TCMalloc全称Thead-Caching Malloc,是google开发的内存分配器,为了方便理解下面的go内存管理,有必要要先熟悉一下TCMalloc. 操作系统对内存管理以页为单位,TCMalloc也是这样,只不过TCMalloc里的Page大小与操作系统里的大小并不一定相等,而是倍数关系.

Golang测试技术

- - Tony Bai
本篇文章内容来源于 Golang核心开发组成员 Andrew Gerrand在Google I/O 2014的一次主题分享“ Testing Techniques”,即介绍使用Golang开发 时会使用到的测试技术(主要针对. 单元测试),包括基本技术、高级技术(并发测试、 mock/fake、竞争条件测试、并发测试、内/外部测 试、vet工具等)等,感觉总结的很全面,这里整理记录下来,希望能给大家带来帮助.

Android内存管理

- - CSDN博客推荐文章
首先Android内存管理机制相当复杂,想要讲清楚比较困难;其次对于绝大多数用户来说,只关心内存够不够用,至于内存如何管理的这种技术细节,不是用户需要去考虑的,写这样一个专题有没有意义. 毕竟我们是用手机,不是来研究手机的. 最后的顾虑是这个专题会不会太技术化了,绝大部分用户不会看或者说缺乏相应的背景.

golang的杀手级应用:docker

- - _不是我干的 _
docker 是 golang 的第一个杀手级应用,发展迅猛, 现在各大云计算平台几乎全都支持 docker 实例,包括 谷歌,亚马逊,阿里云等. golang 本身已经让我惊喜万分,而 docker 更是极大的激发了我对虚拟化的想象. IT 业发展至今,软件和硬件始终是无法分割的两个物体. 就拿最近几年红红火火的智能机时代来说, 很久之前的诺基亚智能机, 软件和硬件相辅相成, 连进入主界面都需要按一个特定的按钮才能进入.

Golang 大杀器之跟踪剖析 trace

- - SegmentFault 最新的文章
在 Go 中有许许多多的分析工具,在之前我有写过一篇 《Golang 大杀器之性能剖析 PProf》 来介绍 PProf,如果有小伙伴感兴趣可以去我博客看看. 但单单使用 PProf 有时候不一定足够完整,因为在真实的程序中还包含许多的隐藏动作,例如 Goroutine 在执行时会做哪些操作. GC 是怎么影响到 Goroutine 的执行的.

基于Golang的微服务——Micro实践

- - IT瘾-tuicool
开始开发前需要先配置好Go的开发环境,可以看我写的 基于Golang的微服务——上手篇. 在 GOPATH目录下的src目录下创建我们的实战项目目录 tech,切换到这个目录. go get github.com/micro/go-micro //用于开发的微服务的RPC框架,是micro架构的基础 go get github.com/micro/protoc-gen-micro // 用于生成Protobuf的代码 go get github.com/micro/micro // 工具集安装,会自动将 micro加入环境变量 复制代码.

Sun JDK 1.6内存管理

- 小丑鱼 - 淘宝JAVA中间件团队博客
分为使用篇、调优篇和实现篇三个部分,使用篇为填鸭式,调优篇为pattern式,实现篇为启发式,三个PPT的目标为:. 1.掌握Sun JDK的内存区域的划分;. 2.掌握Sun JDK垃圾收集器的使用方法和触发时机;. 4.掌握一些基本的GC调优的方法;. 5.了解自动内存管理的常见实现方法,以及Sun JDK所做的优化.

Android内存管理之道

- - CSDN博客移动开发推荐文章
相信一步步走过来的Android从业者,每个人都会遇到OOM的情况. 如何避免和防范OOM的出现,对于每一个程序员来说确实是一门必不可少的能力. 今天我们就谈谈在Android平台下内存的管理之道,开始今天的主题之前,先再次回顾两个概念. 内存泄漏:对象在内存heap堆中中分配的空间,当不再使用或没有引用指向的情况下,仍不能被GC正常回收的情况.

c++之内存管理

- - CSDN博客推荐文章
c++使用3种不同解决方案存储数据,区别是数据保留在内存中的时间. 两种存储持续性为自动:自动变量和寄存器变量(register没有内存地址)(堆栈). 在函数外定义的变量和使用关键字static定义的变量的存储持续性都为静态.. 外部链接性,内部链接性和无链接性. 所有静态变量都有下面的两个初始化特征:.

[译] HotSpot JVM 内存管理

- - IT瘾-dev
HotSpot JVM 内存管理. 更新时间:2018-03-28. 关于 JVM 内存管理或者说垃圾收集,大家可能看过很多的文章了,笔者准备给大家总结下. 这算是系列的第一篇,接下来一段时间会持续更新. 本文主要是翻译《 Memory Management in the Java HotSpot Virtual Machine》白皮书的前四章内容,这是 2006 的老文章了,当年发布这篇文章的还是 Sun Microsystems,以后应该会越来越少人记得这家曾经无比伟大的公司了.