试着把.net的GC讲清楚(1)
什么是GC?
GC(garbage collection)是对内存管理中回收已经不用的内存的一种机制,我们熟知的java和.net都有自己的GC机制,是内存管理的一部分。
为什么会有GC呢?是因为动态的内存分配和分布操作系统是不管的,得各类语言自己实现,例如c和c++自己需要手动管理分配的内存资源,如果不手动释放,那么会造成已经无用的内存不能被操作系统识别使用,也就是所谓的内存泄漏。
.net的GC都是发生在堆(heap),因为这个动态的内存是在堆上分配的。为什么.net 没有像c++一样提供手动管理内存的操作?因为手工管理内存非常容易出问题,开发人员不应花费时间在这个上面,避免人为问题,就找一个管理内存的“人”来,处理这些事情,于是GC就出现了(说一句话,以前自己是c、c++出身,真的一个语言影响一个人的知识广度和深度),大家都不用考虑这些事情,集中在重要的事情上,就像自己找了个管家,而且是专业的,不会出错的那种,省心。
GC有哪些分类?
我了解到的有:Reference Count、Mark and Sweep(升级版Mark and Compact)、Copy and Collection
现在java和.net 使用的是Mark and Compact算法,这个算法是从Mark and Sweep算法演变过来的,下来就讲讲Mark and Sweep。
Mark and Sweep:分为两个阶段,第一阶段标记所有现在还可以使用的对象,第二阶段清除标记的对象之外的内存。
在.net中,GC管理了一组root(由全局对象组成),通过遍历所有的root机器引用的子对象,进行内存中的存活对象的标记,之后就清除未标记为存活的对象。这就是Mark and Sweep算法,但是这个造成了一个问题,就是回收后的内存是成了筛子了,这个时候如果来一个大的对象需要分配内存,那么空余内存总额大于分配对象的大小,但是找不到一个连续的可以容下这个对象大小的内存,这个时候怎么办?其实模拟操作系统,再做一个内存管理的机制就行,在逻辑上看着连续就行了。当然这个不是本次讨论的对象,Mark and Compact解决了内存不连续的问题,因为它把内存做了一次整理(把不相邻的内存移动到一块,看着就连续了)
Mark and Compact:在Mark and Sweep基础上做了一次内存整理,因为内存做整理的时候,对象的引用是不能被使用的,引用地址会变,所以啊,GC的时候,使用到这些对象的线程什么的是会被挂起等待的,也不能经常回收内存,不然性能堪忧,就是因为回收导致挂起了。
啥是0代、1代、2代对象?
要解释这个问题,还得从内存回收时间说起,这里有个假设(其实也是规则)回收内存中所有对象的时间大于回收部分对象的时间,于是就把内存中对象分成了几代,0代对象指最新分配内存的对象,一次类推。其实多少代,这个由GC决定,.net中GC中代数是3代(这个值暂时不能确定能不能改)。
GC怎么管理代对象呢?一般情况下,分配的对象都是0代对象,在分配对象内存时,如果0代对象的内存已经不能容纳新对象了(超过0代对象内存的上限),在gc回收一次0代后,这个还存活的对象代数加1(GC.Collect();GC.GetGeneration(obj)
代码验证过,现在不清楚自动触发是不是回收一次加1),同理如果1代对象超过了1代内存的上限,也会触发gc回收1代对象。那么这个内存回收是定是这样的么?不一定,毕竟微软提供了手动触发gc的功能,就是GC.Collect()
,有兴趣可以翻翻这个方法。
代数的大小,查了很多资料之后,只发现一篇文章说到,.net中0代和1代之和为16MB,2代内存上限非常的大,具体有framework版本和其他一些因素决定的。
//验证回收一次,对象就升一代
Object obj=new Object();
Console.WriteLine(GC.GetGeneration(obj));
GC.Collect();
Console.WriteLine(GC.GetGeneration(obj));
GC.Collect();
Console.WriteLine(GC.GetGeneration(obj));
Finalize、Dispose是啥?如何理解?
.net中有托管资源和非托管资源的分类,托管资源.net自己就可以管理,非托管资源,需要特殊的方法,也就是托管资源在GC的时候,.net可以自己识别,但是非托管资源,GC是自动释放不了的。
什么是非托管资源?这让我想起之前用mfc写windows程序的时候,什么画刷、画笔、com之类的,就是非托管资源,还有数据库连接、文件、套接字之类的也是,哦,还有流之类的都是。
这些非托管资源,一般都需要自己释放资源,.net提供了IDsiposable的接口,实现这个接口的方法,在里面进行资源释放,使用using语句来简化这个非托管资源的释放工作。
Finalize:这个也能用来释放非托管资源,与IDsiposable接口区别是,它的调用时机是不定的,因为它是由GC调用的,GC调用真的不定的,因为调用一次GC调用会降低程序性能(前面说的,内存压缩导致引用需要变化,而因为线程挂起),下面来说说为什么它是由GC调用的。
在创建对象的时候,会把还有析构函数(编译之后,就是Finalize方法,与c++中的析构函数不同)的对象引用存到一个叫做Finalizer Queue的list中,在GC的时候,如果一个对象是无用的,而且在Finalizer Queue里面有引用,此次并不回收,并且会把引用从Finalizer Queue移到Freachable Queue的list中,Freachable Queue的list有内容之后会启动一个线程,然后执行里面的引用的对象的析构函数,执行完毕后把对象的引用删除,等待下次GC的时候,才进行回收此对象。
所以Finalize的特点就是:
- 啥时候调用不定
- 这类对象,需要至少两次GC才能回收。
为什么至少两次,而不是两次,因为.net为我们提供了一个把对象引用放回Finalizer Queue的方法,GC.ReRegisterForFinalize(),如果在Finalize中调用了这个代码,那么就死不了了。
微软不建议使用Finalize方法,就像其他博客中提到的,我们可以把它留作后手,万一那个非托管资源该释放没有释放,可以在Finalize方法中做为最后的保险(算是避免人为原因)。
我确实释放完了非托管资源,就是不想执行Finalize方法,微软也提供了方法了:GC.SuppressFinalize(this),这个方法执行了之后就把这个对象的引用中Finalizer Queue移除了。
LOH是什么?
LOH(large object heap)是为了大对象而专门设计的一个堆,多大的对象会分配到这个堆里面?超过85000个字节的就会。其实这个loh产生原因大对象移动非常的耗时,还不如不移动,例如,3个对象ABC,AB对象大约占个80个字节,C对象占个10000个字节,假设AB对象被回收,那么在移动阶段,就要把10000个字节,往前移动80字节,还不如不移动性能高。这个85000字节也是一个经验值。
既然loh不能移动,那么肯定不能用Mark and Compact中的移动了(使用什么算法现在还不清楚,猜测是Mark and Sweep,或许是特例),并且在只有2代对象回收的时候才进行回收。
GC模式?
- workstation mode:用于单处理器的系统中,频繁回收,从而阻止一次长时间的回收对程序的挂起时间。
- server mode:用于多处理器的系统中,为每个处理器都创建一个GC Heap,该模式特点是,分配内存较大,能不回收就不回收,回收时候耗时太长。
其中有Concurrent GC 工作方式,其中workstation mode和server mode都可以配置,在单处理器上设置为true也不生效,主要用于用户线程在gc时候可以大部分时间和gc线程并发,详细可以参考:https://blogs.msdn.microsoft.com/seteplia/2017/01/05/understanding-different-gc-modes-with-concurrency-visualizer/
啥时候需要手动gc?
资源特别紧张的时候,例如之前面试的一家公司,系统是在azure上,内存什么的特别贵,这个时候手动gc可能其中一种手段了。
最后
其实gc的最耗时还是在算法的选择上,比如Mark and Compact中的把内存合并成连续的,这个才是耗时的,如果内存足够多,根本就不需要考虑移动内存。
或者像我之前提的,再在内存上面做一次内存映射的管理,也可以避免内存不连续的问题,当然肯定会遇到各种各样的问题。
试着把.net的GC讲清楚(1)的更多相关文章
- 试着把.net的GC讲清楚(2)
试着把.net的GC讲清楚(1) 上篇文章说了一些基本概念的东西,然后还有很多东西概念没有头绪,这篇文章我试着解释 GC的回收算法详细步骤? 上篇]文章讲了.net GC的算法是Mark and Co ...
- 试着把.net的GC讲清楚(3)
前两篇写的都是gc的一些概念和细节,这些东西对自己以后写代码有什么用,本篇我就准备将这些内容. root 第一篇文章中讲了GC在遍历存活对象的时候,都是从root开始的,root是一些对象的引用,例如 ...
- 《你不知道的JavaScript下卷》知识点笔记
1. [1, 2, 3] == [1, 2, 3] 返回false [1, 2, 3] == “1, 2, 3” 返回true 2. 如果 < 比较的两个值都是字符串, 就像在b < c中 ...
- 试着讲清楚:js代码运行机制
一. js运行机制 js执行引擎 经常看文章的说到js是带线程的,其实这个说法非常的模糊,准确的是js执行引擎是单线程的,js执行引擎就是js代码的执行器,有了这个概念就可以下来说说js是如何运行的了 ...
- 深入浅出聊优化:从Draw Calls到GC
前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的 ...
- PHP新的垃圾回收机制:Zend GC详解
概述 在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断一个变量空间是否能够被释放的时候是依据这个变量的zval的refcount的值,如果re ...
- GitHub上那些值得一试的JAVA开源库--转
原文地址:http://www.jianshu.com/p/ad40e6dd3789 作为一名程序员,你几乎每天都会使用到GitHub上的那些著名Java第三方库,比如Apache Commons,S ...
- 转:面试题:“你能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?”
转自:http://jeromecen1021.blog.163.com/blog/static/18851527120117274624888/ 面试题目: 地球人都知道,Java有个东西叫垃圾收集 ...
- 优化 从Draw Calls到GC
原文出处: 慕容小匹夫的博客(@慕容小匹夫) 欢迎分享原创到伯乐头条 前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章 ...
随机推荐
- 算法-java代码实现计数排序
计数排序 第10节 计数排序练习题 对于一个int数组,请编写一个计数排序算法,对数组元素排序. 给定一个int数组A及数组的大小n,请返回排序后的数组. 测试样例: [1,2,3,5,2,3], ...
- java开发常用jar包
mail.jar与activation.jar 里面包含了activation.jar和mail.jar两个包.通过里面的类的调用便可以达到发送电子邮件的目的 commons-beanutils.ja ...
- Java数据持久层框架 MyBatis之API学习五(Mapper XML 文件)
对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...
- Java 获取年 月 日 时 分 秒
/** * 英文简写(默认)如:2010-12-01 */ public static String FORMAT_SHORT = "yyyy-MM-dd"; /** * 英文全称 ...
- vuejs axios安装配置与使用
1.安装服务 npm install --save axios vue-axios 2.在main.js import axios from 'axios' import VueAxios from ...
- scrapy_移除内容中html标签
如何移除所获取内容中多余的html标签? 通过w3lib模块和re模块 #!/usr/bin/python3 # -*- coding: UTF-8 -*- __author__ = 'beimenc ...
- mysql存储过程且mybatis调用
首先,需要执行符DELIMITER ,建议用//,即在存储过程开始前定义delimiter //,在结束后加上//,最后加上DELIMITER ; 具体原因@参考文章1写的很清楚,不再赘述. 参考文章 ...
- text和submit框的border问题
A1:在input框中,为type为text的输入框增加边框,它会在内容区域外额外增添,但是在submit框中,它会占用内容区块的一部分作为边框 <style type="text/c ...
- 【转】shell:date 常用方式
在linux下获取时间字符串 命令 date # 以yyyymmdd格式输出23天之前现在这个时刻的时间 $ date +%Y%m%d –date=’23 days ago’ $ date -u Th ...
- pat 1022 digital library
#include <iostream> #include <sstream> #include <string> #include <vector> # ...