精华内容
下载资源
问答
  • mfc学习之分析mfc程序执行过程

    千次阅读 2016-07-06 19:43:31
    在前几天学习自己动手创建了一个mfc程序之后,...和重写了一个初始化函数,没有看到任何的主函数,然后资料上说mfc将入口函数也给封装起来了,因此今天准备跟踪一下mfc程序执行流程,以FirstMFC为例 因为全局变量

         在前几天学习自己动手创建了一个mfc程序之后,生出了一个疑问,作为一个新手,在学习c++标准语法的时候,基本都是控制台程序,而且众所周知程序都会有一个入口点也就是main函数,而在mfc程序中,只是定义了一个全局对象theApp,和重写了一个初始化函数,没有看到任何的主函数,然后资料上说mfc将入口函数也给封装起来了,因此今天准备跟踪一下mfc程序的执行流程,以FirstMFC为例

    因为全局变量先于入口函数执行,因此程序会先执行CMyWinApp myApp; 因为该过程会创建对象,进入构造函数,因此在他之前加断点调试

    按F11进入构造函数内部,会先进入本类的构造函数,再按F11进入父类的构造函数


    在父类的构造函数中发现有两个宏定义AFX_MODULE_STATE和AFX_MODULE_THREAD_STATE,一头雾水,在网上查阅了很多资料也没得出个所以然,在这里分享一篇分析较好的文章

    http://blog.163.com/maple_zh@126/blog/static/10712959820132219189932/

    继续跟代码发现这两个宏其实是两个类,都派生自一个叫CNoTrackObject的类,进入这个类,发现它冲在了new跟delete操作符

    至于这个类出现的意义,在网上查询得到的解释是用来记录内存块和给每一个对象分配一个单独的内存块(有待考证),最终得到信息是

     

    AFX_MODULE_STATE     定义的变量记录当前程序模块状态信息

    AFX_MODULE_THREAD_STATE 定义的变量记录当前程序线程状态信息


    而用pModuleState指针和pThreadState指针分别获取了_afxThreadState和m_thread这两个全局变量的地址



    执行到断点处可以发现,mfc将当前myApp对象的地址保存到了AFX_MODULE_THREAD_STATE类的成员变量CWinThread* m_pCurrentWinApp中,即CWinApp(CMyWinApp的父类)拿到了CMyWinApp的地址,而下面的AfxGetThread又重新拿到了myApp的地址(不理解)

    CWinThread* AFXAPI AfxGetThread()
    {
    	// check for current thread in module thread state
    	AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
    	CWinThread* pThread = pState->m_pCurrentWinThread;
    	return pThread;
    }

    继续执行mfc又将将当前myApp对象的地址保存到了 AFX_MODULE_STATE 类的成员变量CWinApp* m_pCurrentWinApp中

    从这里可以看出CwinApp的构造函数主要完成了以下几个功能

    1) 将myApp对象地址保存到当前程序模块状态信息中。

    2) 将myApp对象地址保存到当前程序线程状态信息中。

    3) AfxGetThread/AfxGetApp – 返回myApp对象地址。

    因此可以猜测,当前myApp对象地址会在之后的窗口创建过程中会用到

    至此父类的构造函数执行完毕,退出父类构造函数开始执行子类构造,子类构造执行完毕应该开始执行main函数,也就是需要寻找的程序入口点,由于不知道函数入口点在哪,但是可以肯定的是入口函数会调用CMyWinApp::InitInstance函数来实现窗口的创建过程,因此可以借助vs的调用堆栈工具来查找函数之间的调用关系,如

    进入入口函数

    点击单步调试进入AfxWinMain

    CWinThread* pThread = AfxGetThread();//获取myApp对象地址
    	CWinApp* pApp = AfxGetApp();//获取myApp对象地址
    
    	// AFX internal initialization
    	if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))//初始化mfc库
    		goto InitFailure;
    
    	// App global initializations (rare)
    	if (pApp != NULL && !pApp->InitApplication())//初始化应用程序
    		goto InitFailure;
    
    	// Perform specific initializations
    	if (!pThread->InitInstance())
    	{
    		if (pThread->m_pMainWnd != NULL)
    		{
    			TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
    			pThread->m_pMainWnd->DestroyWindow();
    		}
    		nReturnCode = pThread->ExitInstance();
    		goto InitFailure;
    	}
    	nReturnCode = pThread->Run();
    由代码可以看出在经过一系列初始化之后,执行到pThread->InitInstance,而pThread为基类指针存的是子类myApp的地址,由多态可以得到程序会调用我们重写的 CMyWinApp::InitInstance函数



    此时程序回到我们写的初始化函数开始创建窗口

    
    



    展开全文
  • 理解MVC应用程序执行过程

    千次阅读 2009-10-22 02:33:00
    Requests to an ASP.NET MVC-based Web application first pass through the UrlRoutingModule object, which is an HTTP module. This module parses the request and performs route selection. The UrlRoutingMod
    Requests to an ASP.NET MVC-based Web application first pass through the UrlRoutingModule object, which is an HTTP module. This module parses the request and performs route selection. The UrlRoutingModule object selects the first route object that matches the current request. (A route object is a class that implements RouteBase, and is typically an instance of the Route class.) If no routes match, the UrlRoutingModule object does nothing and lets the request fall back to the regular ASP.NET or IIS request processing.

    当请求基于ASP.NET MVC的应用程序时首先经过UrlRoutingModule的对象,它是一个Http模块。这个模块解析请求并根据路由选择执行。UrlRoutingModule对象先与第一个route对象进行匹配(route对象继承自RouteBase类的典型的Route类的实例。)如果没有匹配成功,那么UrlRoutingModule什么也不做,将控制权交给ASP.NET或IIS继续处理请求。

    From the selected Route object, the UrlRoutingModule object obtains the IRouteHandler object that is associated with the Route object. Typically, in an MVC application, this will be an instance of MvcRouteHandler. The IRouteHandler instance creates an IHttpHandler object and passes it the IHttpContext object. By default, the IHttpHandler instance for MVC is the MvcHandler object. The MvcHandler object then selects the controller that will ultimately handle the request.

    从Route选择的对象后UrlRoutingModule模块获得与之相关的IRoteHandler对象,通常在一个MVC应用程序中,这将是MvcRouteHandler的实例。IRouteHandler实例创建一个IHttpHandler对象并传递到IHttpContext对象中。默认情况下,这个MVC的IHttpHandler实例是MvcHandler对象,最后在MvcHandler对象中选定哪个controller来继续请求。

    Note:

    When an ASP.NET MVC Web application runs in IIS 7.0, no file name extension is required for MVC projects. However, in IIS 6.0, the handler requires that you map the .mvc file name extension to the ASP.NET ISAPI DLL.

    提示:

    如果ASP.NET MVC Web应用程序运行在IIS7.0中允许MVC工程没有文件扩展名的请求,可是IIS 6.0要求你必须为请求处理程序映射护展名为.mvc的ASP.NET ISAPI DLL

    The module and handler are the entry points to the ASP.NET MVC framework. They perform the following actions:

    Select the appropriate controller in an MVC Web application.

    Obtain a specific controller instance.

    Call the controller's Execute method.

    ASP.NET MVC framework以handler和module作为切入点,进行了以下的工作:

    在MVC Web application中选择相应的contoller。

    获取这个controller实例。

    调用controller的Execute方法。

    The following table lists the stages of execution for an MVC Web project.

    Stage

    Details

    Receive first request for the application

    In the Global.asax file, Route objects are added to the RouteTable object.

    Perform routing

    The UrlRoutingModule module uses the first matching Route object in the RouteTable collection to create the RouteData object, which it then uses to create a RequestContext (IHttpContext) object.

    Create MVC request handler

    The MvcRouteHandler object creates an instance of the MvcHandler class and passes it the RequestContext instance.

    Create controller

    The MvcHandler object uses the RequestContext instance to identify the IControllerFactory object (typically an instance of the DefaultControllerFactory class) to create the controller instance with.

    Execute controller

    The MvcHandler instance calls the controller's Execute method.

    Invoke action

    Most controllers inherit from the Controller base class. For controllers that do so, the ControllerActionInvoker object that is associated with the controller determines which action method of the controller class to call, and then calls that method.

    Execute result

    A typical action method might receive user input, prepare the appropriate response data, and then execute the result by returning a result type. The built-in result types that can be executed include the following: ViewResult (which renders a view and is the most-often used result type), RedirectToRouteResult, RedirectResult, ContentResult, JsonResult, and EmptyResult.

    以下表格是ASP.NET MVC执行周期中所处的阶段:

    阶段

    介绍

    收到第一次请求

    在Global.asax文件中将Route对象依次增加到RouteTable对象。

    执行路由

    UrlRoutingModule模块先从RouteTable集合中依次匹配Route,如果匹配到后就创建RouteData对象,然后使用它来创建RequestContext(IHttpContext)对象。

    创建请求MVC处理模块

    MvcRouteHandler对象创建一个MvcHandler的实例类型并传入RequestContext的实例

    创建controller

    MvcHandler对象使用RequestContext实例以确定IControllerFactory对象(通常是DefaultControllerFactory类的实例)来创建controller实例。

    执行controller

    The MvcHandler instance calls the controller's Execute method.

    MvcHandler实例调用controller的Execute方法。

    调用action

    大多数控制器继承自Controller基类,ControllerActionInvoker对象确实与其关联的Controller类中的action然后调用这个方法。

    执行结果

    通常action方法可以接收用户输入并得到结果,然后返回一个result类型。你可以使用几种内置的返回结果:

    ViewResult(呈现一个视图,这是最常用的返回结果)

    RedirectToRouteResult、RedirectResult、ContentResult、JsonResult和EmptyResult

    展开全文
  • EF 的三种设计模型CodeFirst,ModelFirst ,DBFirst三种 在软件的实际开发中最常用的就是后两种。下面简单介绍两种设计模型的区别和两种更新的区别 (1)ModelFirst 顾名思义就是首先设计实体模型,之后根据实体...

    一、EF 的三种设计模型CodeFirstModelFirst ,DBFirst三种 在软件的实际开发中最常用的就是后两种。下面简单介绍两种设计模型的区别和两种更新的区别,如果你还不知道如何进行三种设计模式的设计,请参考我另外的几篇文章:EF中的DBFirst实例尝试 Entity Framework POCO功能与CodeFirst的结合

    1、ModelFirst 顾名思义就是首先设计实体模型,之后根据实体模型实现到数据库的映射。

    2、DBFirst 就是先进行数据库的设计,之后根据数据库生成实体数据模型。具体区别就是在新建实体数据模型时,实体模型向导中提示的根据数据库生成还是空模型,如图1所示,


                                                    

                                                                                                                                                     图

    选择根据数据库生成就是DBFirst ,选择空模型就是ModelFirst 


    二、EF框架的原理就是把实体类的变化通过映射反应到数据库中去,实现表的增删改查。对象上下文是实体类操作数据库的API。应用程序对实体类进行的增删改查操作会经由对象上下文进行映射,最终转换为SQL脚本语言,然后执行,最终实现对表的增删改查。当需要修改表的结构时,我们可以选择根据模型更新数据库和根据数据库更新模型两种。双击打开实体模型文件,如图2所示

                                                                                                 

                                                                                                                                      图2

    在空白页右击会显示根据模型更新数据库和根据数据库更新模型。

    下面着重讲解根据模型更新数据库:

    以下摘录了修改实体模型(增加一列)之后生成的SQL 脚本语言

     

    SET QUOTED_IDENTIFIER OFF;

    GO

    USE [Mine];

    GO

    IF SCHEMA_ID(N'dbo') IS NULL EXECUTE(N'CREATE SCHEMA [dbo]');

    GO

    -- --------------------------------------------------

    -- Dropping existing FOREIGN KEY constraints

    -- --------------------------------------------------

    -- --------------------------------------------------

    -- Dropping existing tables

    -- --------------------------------------------------

    IF OBJECT_ID(N'[dbo].[Accounts]', 'U') IS NOT NULL

        DROP TABLE [dbo].[Accounts];

    GO

    -- --------------------------------------------------

    -- Creating all tables

    -- --------------------------------------------------

    -- Creating table 'Accounts'

    CREATE TABLE [dbo].[Accounts] (

        [ID] int IDENTITY(1,1) NOT NULL,

        [Name] nvarchar(max)  NOT NULL,

        [Sex] nvarchar(max)  NOT NULL,

        [Sphone] nvarchar(max)  NOT NULL

    );

    GO

    -- --------------------------------------------------

    -- Creating all PRIMARY KEY constraints

    -- --------------------------------------------------

     

    -- Creating primary key on [ID] in table 'Accounts'

    ALTER TABLE [dbo].[Accounts]

    ADD CONSTRAINT [PK_Accounts]

        PRIMARY KEY CLUSTERED ([ID] ASC);

    GO

    -- --------------------------------------------------

    -- Creating all FOREIGN KEY constraints

    -- --------------------------------------------------

    -- --------------------------------------------------

    -- Script has ended

    -- --------------------------------------------------

    大家从上面可以看到,这个SQL脚本的内容会先把各种外键约束和表给删除掉(理所当然,原先表中存在的数据也一并删除了,在实际的软件开发中此种操作会造成毁灭性的破坏),之后会再次创建一个新的表格。为避免此种情况的出生,当需要修改表的结构时最好的操作就是先修改数据库,然后选择根据数据库更新模型。

    以上只是我的一点见解,不对之处还请各位大牛指出。

    展开全文
  • 详解 JVM Garbage First(G1) 垃圾收集器

    万次阅读 多人点赞 2018-02-06 17:25:36
    详解 JVM Garbage First(G1) 垃圾收集器 前言 Garbage First(G1)是垃圾收集领域的最新成果,同时也是HotSpot在JVM上力推的垃圾收集器,并赋予取代CMS的使命。如果使用Java 8/9,那么有很大可能希望对G1收集...

    前言
    Garbage First(G1)是垃圾收集领域的最新成果,同时也是HotSpot在JVM上力推的垃圾收集器,并赋予取代CMS的使命。如果使用Java 8/9,那么有很大可能希望对G1收集器进行评估。本文详细首先对JVM其他的垃圾收集器进行总结,并与G1进行了简单的对比;然后通过G1的内存模型、G1的活动周期,对G1的工作机制进行了介绍;同时还在介绍过程中,描述了可能需要引起注意的优化点。笔者希望通过本文,让有一定JVM基础的读者能尽快掌握G1的知识点。

    第一章 概述

    G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术。同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。

    第二章 JVM GC收集器的回顾与比较

    JVM GC收集器成员

    从JDK3(1.3)开始,HotSpot团队一直努力朝着高效收集、减少停顿(STW: Stop The World)的方向努力,也贡献了从串行到CMS乃至最新的G1在内的一系列优秀的垃圾收集器。上图展示了JDK的垃圾回收大家庭,以及相互之间的组合关系,下面就几种典型的组合应用进行简单的介绍。

    串行收集器

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VllqSnjk-1600416354690)(https://c1.staticflickr.com/5/4603/28345836579_8dff90eb76_z.jpg)]

    串行收集器组合 Serial + Serial Old

    开启选项:-XX:+SerialGC

    串行收集器是最基本、发展时间最长、久经考验的垃圾收集器,也是client模式下的默认收集器配置。

    串行收集器采用单线程stop-the-world的方式进行收集。当内存不足时,串行GC设置停顿标识,待所有线程都进入安全点(Safepoint)时,应用线程暂停,串行GC开始工作,采用单线程方式回收空间并整理内存。单线程也意味着复杂度更低、占用内存更少,但同时也意味着不能有效利用多核优势。事实上,串行收集器特别适合堆内存不高、单核甚至双核CPU的场合。

    并行收集器

    并行收集器组合

    并行收集器组合 Parallel Scavenge + Parallel Old

    开启选项:-XX:+UseParallelGC-XX:+UseParallelOldGC(可互相激活)

    并行收集器是以关注吞吐量为目标的垃圾收集器,也是server模式下的默认收集器配置,对吞吐量的关注主要体现在年轻代Parallel Scavenge收集器上。

    并行收集器与串行收集器工作模式相似,都是stop-the-world方式,只是暂停时并行地进行垃圾收集。年轻代采用复制算法,老年代采用标记-整理,在回收的同时还会对内存进行压缩。关注吞吐量主要指年轻代的Parallel Scavenge收集器,通过两个目标参数-XX:MaxGCPauseMills-XX:GCTimeRatio,调整新生代空间大小,来降低GC触发的频率。并行收集器适合对吞吐量要求远远高于延迟要求的场景,并且在满足最差延时的情况下,并行收集器将提供最佳的吞吐量。

    并发标记清除收集器

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LeSnO6cw-1600416354703)(https://c1.staticflickr.com/5/4740/40093687062_7383cd1b49_z.jpg)]

    并发标记清除收集器组合 ParNew + CMS + Serial Old

    开启选项:-XX:+UseConcMarkSweepGC

    并发标记清除(CMS)是以关注延迟为目标、十分优秀的垃圾回收算法,开启后,年轻代使用STW式的并行收集,老年代回收采用CMS进行垃圾回收,对延迟的关注也主要体现在老年代CMS上。

    年轻代ParNew与并行收集器类似,而老年代CMS每个收集周期都要经历:初始标记、并发标记、重新标记、并发清除。其中,初始标记以STW的方式标记所有的根对象;并发标记则同应用线程一起并行,标记出根对象的可达路径;在进行垃圾回收前,CMS再以一个STW进行重新标记,标记那些由mutator线程(指引起数据变化的线程,即应用线程)修改而可能错过的可达对象;最后得到的不可达对象将在并发清除阶段进行回收。值得注意的是,初始标记和重新标记都已优化为多线程执行。CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。

    但是CMS并不完美,它有以下缺点:

    1. 由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间;
    2. 标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC。

    Garbage First

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s3YrrBdC-1600416354705)(https://c1.staticflickr.com/5/4655/28345836209_7b70465317_b.jpg)]

    Garbage First (G1)

    开启选项:-XX:+UseG1GC

    之前介绍的几组垃圾收集器组合,都有几个共同点:

    1. 年轻代、老年代是独立且连续的内存块;
    2. 年轻代收集使用单eden、双survivor进行复制算法;
    3. 老年代收集必须扫描整个老年代区域;
    4. 都是以尽可能少而块地执行GC为设计原则。

    G1垃圾收集器也是以关注延迟为目标、服务器端应用的垃圾收集器,被HotSpot团队寄予取代CMS的使命,也是一个非常具有调优潜力的垃圾收集器。虽然G1也有类似CMS的收集动作:初始标记、并发标记、重新标记、清除、转移回收,并且也以一个串行收集器做担保机制,但单纯地以类似前三种的过程描述显得并不是很妥当。事实上,G1收集与以上三组收集器有很大不同:

    1. G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。因此,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
    2. G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
    3. G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
    4. G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。

    第三章 G1的内存模型

    分区概念

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wqGru6eQ-1600416354708)(https://c1.staticflickr.com/5/4678/40093686972_25da5b859f_z.jpg)]

    分区

    分区 Region

    G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。

    卡片

    卡片 Card

    在每个分区内部又被分成了若干个大小为512 Byte卡片(Card),标识堆内存最小可用粒度所有分区的卡片将会记录在全局卡片表(Global Card Table)中,分配的对象会占用物理上连续的若干个卡片,当查找对分区内对象的引用时便可通过记录卡片来查找该引用对象(见RSet)。每次对内存的回收,都是对指定分区的卡片进行处理。

    堆 Heap

    G1同样可以通过-Xms/-Xmx来指定堆空间大小。当发生年轻代收集或混合收集时,通过计算GC与应用的耗费时间比,自动调整堆空间大小。如果GC频率太高,则通过增加堆尺寸,来减少GC频率,相应地GC占用的时间也随之降低;目标参数-XX:GCTimeRatio即为GC与应用的耗费时间比,G1默认为9,而CMS默认为99,因为CMS的设计原则是耗费在GC上的时间尽可能的少。另外,当空间不足,如对象空间分配或转移失败时,G1会首先尝试增加堆空间,如果扩容失败,则发起担保的Full GC。Full GC后,堆尺寸计算结果也会调整堆空间。

    分代模型

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNrQ7ADx-1600416354711)(https://c1.staticflickr.com/5/4621/28345836009_65a54854be_z.jpg)]

    分代

    分代 Generation

    分代垃圾收集可以将关注点集中在最近被分配的对象上,而无需整堆扫描,避免长命对象的拷贝,同时独立收集有助于降低响应时间。虽然分区使得内存分配不再要求紧凑的内存空间,但G1依然使用了分代的思想。与其他垃圾收集器类似,G1将内存在逻辑上划分为年轻代和老年代,其中年轻代又划分为Eden空间和Survivor空间。但年轻代空间并不是固定不变的,当现有年轻代分区占满时,JVM会分配新的空闲分区加入到年轻代空间。

    整个年轻代内存会在初始空间-XX:G1NewSizePercent(默认整堆5%)与最大空间-XX:G1MaxNewSizePercent(默认60%)之间动态变化,且由参数目标暂停时间-XX:MaxGCPauseMillis(默认200ms)、需要扩缩容的大小以及分区的已记忆集合(RSet)计算得到。当然,G1依然可以设置固定的年轻代大小(参数-XX:NewRatio-Xmn),但同时暂停目标将失去意义。

    本地分配缓冲

    本地分配缓冲 Local allocation buffer (Lab)

    值得注意的是,由于分区的思想,每个线程均可以"认领"某个分区用于线程本地的内存分配,而不需要顾及分区是否连续。因此,每个应用线程和GC线程都会独立的使用分区,进而减少同步时间,提升GC效率,这个分区称为本地分配缓冲区(Lab)。

    其中,应用线程可以独占一个本地缓冲区(TLAB)来创建的对象,而大部分都会落入Eden区域(巨型对象或分配失败除外),因此TLAB的分区属于Eden空间;而每次垃圾收集时,每个GC线程同样可以独占一个本地缓冲区(GCLAB)用来转移对象,每次回收会将对象复制到Suvivor空间或老年代空间;对于从Eden/Survivor空间晋升(Promotion)到Survivor/老年代空间的对象,同样有GC独占的本地缓冲区进行操作,该部分称为晋升本地缓冲区(PLAB)。

    分区模型

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RLElAZ8f-1600416354719)(https://c1.staticflickr.com/5/4619/40093686872_ce639c1121_z.jpg)]

    G1对内存的使用以分区(Region)为单位,而对对象的分配则以卡片(Card)为单位。

    巨型对象

    巨型对象 Humongous Region

    一个大小达到甚至超过分区大小一半的对象称为巨型对象(Humongous Object)。当线程为巨型分配空间时,不能简单在TLAB进行分配,因为巨型对象的移动成本很高,而且有可能一个分区不能容纳巨型对象。因此,巨型对象会直接在老年代分配,所占用的连续空间称为巨型分区(Humongous Region)。G1内部做了一个优化,一旦发现没有引用指向巨型对象,则可直接在年轻代收集周期中被回收。

    巨型对象会独占一个、或多个连续分区,其中第一个分区被标记为开始巨型(StartsHumongous),相邻连续分区被标记为连续巨型(ContinuesHumongous)。由于无法享受Lab带来的优化,并且确定一片连续的内存空间需要扫描整堆,因此确定巨型对象开始位置的成本非常高,如果可以,应用程序应避免生成巨型对象。

    已记忆集合

    已记忆集合 Remember Set (RSet)

    在串行和并行收集器中,GC通过整堆扫描,来确定对象是否处于可达路径中。然而G1为了避免STW式的整堆扫描,在每个分区记录了一个已记忆集合(RSet),内部类似一个反向指针,记录引用分区内对象的卡片索引。当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况。

    事实上,并非所有的引用都需要记录在RSet中,如果一个分区确定需要扫描,那么无需RSet也可以无遗漏的得到引用关系。那么引用源自本分区的对象,当然不用落入RSet中;同时,G1 GC每次都会对年轻代进行整体收集,因此引用源自年轻代的对象,也不需要在RSet中记录。最后只有老年代的分区可能会有RSet记录,这些分区称为拥有RSet分区(an RSet’s owning region)。

    Per Region Table

    Per Region Table (PRT)

    RSet在内部使用Per Region Table(PRT)记录分区的引用情况。由于RSet的记录要占用分区的空间,如果一个分区非常"受欢迎",那么RSet占用的空间会上升,从而降低分区的可用空间。G1应对这个问题采用了改变RSet的密度的方式,在PRT中将会以三种模式记录引用:

    • 稀少:直接记录引用对象的卡片索引
    • 细粒度:记录引用对象的分区索引
    • 粗粒度:只记录引用情况,每个分区对应一个比特位

    由上可知,粗粒度的PRT只是记录了引用数量,需要通过整堆扫描才能找出所有引用,因此扫描速度也是最慢的。

    收集集合 (CSet)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2WcZKCzT-1600416354721)(https://c1.staticflickr.com/5/4766/40126693251_d74183c2c6.jpg)]

    收集集合 CSet

    收集集合(CSet)代表每次GC暂停时回收的一系列目标分区。在任意一次收集暂停中,CSet所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中。因此无论是年轻代收集,还是混合收集,工作的机制都是一致的。年轻代收集CSet只容纳年轻代分区,而混合收集会通过启发式算法,在老年代候选回收分区中,筛选出回收收益最高的分区添加到CSet中。

    候选老年代分区的CSet准入条件,可以通过活跃度阈值-XX:G1MixedGCLiveThresholdPercent(默认85%)进行设置,从而拦截那些回收开销巨大的对象;同时,每次混合收集可以包含候选老年代分区,可根据CSet对堆的总大小占比-XX:G1OldCSetRegionThresholdPercent(默认10%)设置数量上限。

    由上述可知,G1的收集都是根据CSet进行操作的,年轻代收集与混合收集没有明显的不同,最大的区别在于两种收集的触发条件。

    年轻代收集集合

    年轻代收集集合 CSet of Young Collection

    应用线程不断活动后,年轻代空间会被逐渐填满。当JVM分配对象到Eden区域失败(Eden区已满)时,便会触发一次STW式的年轻代收集。在年轻代收集中,Eden分区存活的对象将被拷贝到Survivor分区;原有Survivor分区存活的对象,将根据任期阈值(tenuring threshold)分别晋升到PLAB中,新的survivor分区和老年代分区。而原有的年轻代分区将被整体回收掉。

    同时,年轻代收集还负责维护对象的年龄(存活次数),辅助判断老化(tenuring)对象晋升的时候是到Survivor分区还是到老年代分区。年轻代收集首先先将晋升对象尺寸总和、对象年龄信息维护到年龄表中,再根据年龄表、Survivor尺寸、Survivor填充容量-XX:TargetSurvivorRatio(默认50%)、最大任期阈值-XX:MaxTenuringThreshold(默认15),计算出一个恰当的任期阈值,凡是超过任期阈值的对象都会被晋升到老年代。

    混合收集集合

    混合收集集合 CSet of Mixed Collection

    年轻代收集不断活动后,老年代的空间也会被逐渐填充。当老年代占用空间超过整堆比IHOP阈值-XX:InitiatingHeapOccupancyPercent(默认45%)时,G1就会启动一次混合垃圾收集周期。为了满足暂停目标,G1可能不能一口气将所有的候选分区收集掉,因此G1可能会产生连续多次的混合收集与应用线程交替执行,每次STW的混合收集与年轻代收集过程相类似。

    为了确定包含到年轻代收集集合CSet的老年代分区,JVM通过参数混合周期的最大总次数-XX:G1MixedGCCountTarget(默认8)、堆废物百分比-XX:G1HeapWastePercent(默认5%)。通过候选老年代分区总数与混合周期最大总次数,确定每次包含到CSet的最小分区数量;根据堆废物百分比,当收集达到参数时,不再启动新的混合收集。而每次添加到CSet的分区,则通过计算得到的GC效率进行安排。

    第四章 G1的活动周期

    G1垃圾收集活动汇总

    G1垃圾收集活动周期图

    祭出一张总图

    RSet的维护

    由于不能整堆扫描,又需要计算分区确切的活跃度,因此,G1需要一个增量式的完全标记并发算法,通过维护RSet,得到准确的分区引用信息。在G1中,RSet的维护主要来源两个方面:写栅栏(Write Barrier)和并发优化线程(Concurrence Refinement Threads)

    栅栏

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GmvZOTP3-1600416354724)(https://c1.staticflickr.com/5/4658/40093686702_9d689b29e9_z.jpg)]

    栅栏 Barrier

    我们首先介绍一下栅栏(Barrier)的概念。栅栏是指在原生代码片段中,当某些语句被执行时,栅栏代码也会被执行。而G1主要在赋值语句中,使用写前栅栏(Pre-Write Barrrier)和写后栅栏(Post-Write Barrrier)。事实上,写栅栏的指令序列开销非常昂贵,应用吞吐量也会根据栅栏复杂度而降低。

    写前栅栏 Pre-Write Barrrier

    即将执行一段赋值语句时,等式左侧对象将修改引用到另一个对象,那么等式左侧对象原先引用的对象所在分区将因此丧失一个引用,那么JVM就需要在赋值语句生效之前,记录丧失引用的对象。JVM并不会立即维护RSet,而是通过批量处理,在将来RSet更新(见SATB)。

    写后栅栏 Post-Write Barrrier

    当执行一段赋值语句后,等式右侧对象获取了左侧对象的引用,那么等式右侧对象所在分区的RSet也应该得到更新。同样为了降低开销,写后栅栏发生后,RSet也不会立即更新,同样只是记录此次更新日志,在将来批量处理(见Concurrence Refinement Threads)。

    起始快照算法

    起始快照算法 Snapshot at the beginning (SATB)

    Taiichi Tuasa贡献的增量式完全并发标记算法起始快照算法(SATB),主要针对标记-清除垃圾收集器的并发标记阶段,非常适合G1的分区块的堆结构,同时解决了CMS的主要烦恼:重新标记暂停时间长带来的潜在风险。

    SATB会创建一个对象图,相当于堆的逻辑快照,从而确保并发标记阶段所有的垃圾对象都能通过快照被鉴别出来。当赋值语句发生时,应用将会改变了它的对象图,那么JVM需要记录被覆盖的对象。因此写前栅栏会在引用变更前,将值记录在SATB日志或缓冲区中。每个线程都会独占一个SATB缓冲区,初始有256条记录空间。当空间用尽时,线程会分配新的SATB缓冲区继续使用,而原有的缓冲去则加入全局列表中。最终在并发标记阶段,并发标记线程(Concurrent Marking Threads)在标记的同时,还会定期检查和处理全局缓冲区列表的记录,然后根据标记位图分片的标记位,扫描引用字段来更新RSet。此过程又称为并发标记/SATB写前栅栏。

    并发优化线程

    并发优化线程 Concurrence Refinement Threads

    G1中使用基于Urs Hölzle的快速写栅栏,将栅栏开销缩减到2个额外的指令。栅栏将会更新一个card table type的结构来跟踪代间引用。

    当赋值语句发生后,写后栅栏会先通过G1的过滤技术判断是否是跨分区的引用更新,并将跨分区更新对象的卡片加入缓冲区序列,即更新日志缓冲区或脏卡片队列。与SATB类似,一旦日志缓冲区用尽,则分配一个新的日志缓冲区,并将原来的缓冲区加入全局列表中。

    并发优化线程(Concurrence Refinement Threads),只专注扫描日志缓冲区记录的卡片来维护更新RSet,线程最大数目可通过-XX:G1ConcRefinementThreads(默认等于-XX:ParellelGCThreads)设置。并发优化线程永远是活跃的,一旦发现全局列表有记录存在,就开始并发处理。如果记录增长很快或者来不及处理,那么通过阈值-X:G1ConcRefinementGreenZone/-XX:G1ConcRefinementYellowZone/-XX:G1ConcRefinementRedZone,G1会用分层的方式调度,使更多的线程处理全局列表。如果并发优化线程也不能跟上缓冲区数量,则Mutator线程(Java应用线程)会挂起应用并被加进来帮助处理,直到全部处理完。因此,必须避免此类场景出现。

    并发标记周期

    并发标记周期 Concurrent Marking Cycle

    并发标记周期是G1中非常重要的阶段,这个阶段将会为混合收集周期识别垃圾最多的老年代分区。整个周期完成根标记、识别所有(可能)存活对象,并计算每个分区的活跃度,从而确定GC效率等级。

    当达到IHOP阈值-XX:InitiatingHeapOccupancyPercent(老年代占整堆比,默认45%)时,便会触发并发标记周期。整个并发标记周期将由初始标记(Initial Mark)、根分区扫描(Root Region Scanning)、并发标记(Concurrent Marking)、重新标记(Remark)、清除(Cleanup)几个阶段组成。其中,初始标记(随年轻代收集一起活动)、重新标记、清除是STW的,而并发标记如果来不及标记存活对象,则可能在并发标记过程中,G1又触发了几次年轻代收集。

    并发标记线程

    并发标记线程 Concurrent Marking Threads

    并发标记位图过程

    要标记存活的对象,每个分区都需要创建位图(Bitmap)信息来存储标记数据,来确定标记周期内被分配的对象。G1采用了两个位图Previous Bitmap、Next Bitmap,来存储标记数据,Previous位图存储上次的标记数据,Next位图在标记周期内不断变化更新,同时Previous位图的标记数据也越来越过时,当标记周期结束后Next位图便替换Previous位图,成为上次标记的位图。同时,每个分区通过顶部开始标记(TAMS),来记录已标记过的内存范围。同样的,G1使用了两个顶部开始标记Previous TAMS(PTAMS)、Next TAMS(NTAMS),记录已标记的范围。

    在并发标记阶段,G1会根据参数-XX:ConcGCThreads(默认GC线程数的1/4,即-XX:ParallelGCThreads/4),分配并发标记线程(Concurrent Marking Threads),进行标记活动。每个并发线程一次只扫描一个分区,并通过"手指"指针的方式优化获取分区。并发标记线程是爆发式的,在给定的时间段拼命干活,然后休息一段时间,再拼命干活。

    每个并发标记周期,在初始标记STW的最后,G1会分配一个空的Next位图和一个指向分区顶部(Top)的NTAMS标记。Previous位图记录的上次标记数据,上次的标记位置,即PTAMS,在PTAMS与分区底部(Bottom)的范围内,所有的存活对象都已被标记。那么,在PTAMS与Top之间的对象都将是隐式存活(Implicitly Live)对象。在并发标记阶段,Next位图吸收了Previous位图的标记数据,同时每个分区都会有新的对象分配,则Top与NTAMS分离,前往更高的地址空间。在并发标记的一次标记中,并发标记线程将找出NTAMS与PTAMS之间的所有存活对象,将标记数据存储在Next位图中。同时,在NTAMS与Top之间的对象即成为已标记对象。如此不断地更新Next位图信息,并在清除阶段与Previous位图交换角色。

    初始标记

    初始标记 Initial Mark

    初始标记(Initial Mark)负责标记所有能被直接可达的根对象(原生栈对象、全局对象、JNI对象),根是对象图的起点,因此初始标记需要将Mutator线程(Java应用线程)暂停掉,也就是需要一个STW的时间段。事实上,当达到IHOP阈值时,G1并不会立即发起并发标记周期,而是等待下一次年轻代收集,利用年轻代收集的STW时间段,完成初始标记,这种方式称为借道(Piggybacking)。在初始标记暂停中,分区的NTAMS都被设置到分区顶部Top,初始标记是并发执行,直到所有的分区处理完。

    根分区扫描

    根分区扫描 Root Region Scanning

    在初始标记暂停结束后,年轻代收集也完成的对象复制到Survivor的工作,应用线程开始活跃起来。此时为了保证标记算法的正确性,所有新复制到Survivor分区的对象,都需要被扫描并标记成根,这个过程称为根分区扫描(Root Region Scanning),同时扫描的Suvivor分区也被称为根分区(Root Region)。根分区扫描必须在下一次年轻代垃圾收集启动前完成(并发标记的过程中,可能会被若干次年轻代垃圾收集打断),因为每次GC会产生新的存活对象集合。

    并发标记

    并发标记 Concurrent Marking

    和应用线程并发执行,并发标记线程在并发标记阶段启动,由参数-XX:ConcGCThreads(默认GC线程数的1/4,即-XX:ParallelGCThreads/4)控制启动数量,每个线程每次只扫描一个分区,从而标记出存活对象图。在这一阶段会处理Previous/Next标记位图,扫描标记对象的引用字段。同时,并发标记线程还会定期检查和处理STAB全局缓冲区列表的记录,更新对象引用信息。参数-XX:+ClassUnloadingWithConcurrentMark会开启一个优化,如果一个类不可达(不是对象不可达),则在重新标记阶段,这个类就会被直接卸载。所有的标记任务必须在堆满前就完成扫描,如果并发标记耗时很长,那么有可能在并发标记过程中,又经历了几次年轻代收集。如果堆满前没有完成标记任务,则会触发担保机制,经历一次长时间的串行Full GC。

    存活数据计算

    存活数据计算 Live Data Accounting

    存活数据计算(Live Data Accounting)是标记操作的附加产物,只要一个对象被标记,同时会被计算字节数,并计入分区空间。只有NTAMS以下的对象会被标记和计算,在标记周期的最后,Next位图将被清空,等待下次标记周期。

    重新标记

    重新标记 Remark

    重新标记(Remark)是最后一个标记阶段。在该阶段中,G1需要一个暂停的时间,去处理剩下的SATB日志缓冲区和所有更新,找出所有未被访问的存活对象,同时安全完成存活数据计算。这个阶段也是并行执行的,通过参数-XX:ParallelGCThread可设置GC暂停时可用的GC线程数。同时,引用处理也是重新标记阶段的一部分,所有重度使用引用对象(弱引用、软引用、虚引用、最终引用)的应用都会在引用处理上产生开销。

    清除

    清除 Cleanup

    紧挨着重新标记阶段的清除(Clean)阶段也是STW的。Previous/Next标记位图、以及PTAMS/NTAMS,都会在清除阶段交换角色。清除阶段主要执行以下操作:

    1. RSet梳理,启发式算法会根据活跃度和RSet尺寸对分区定义不同等级,同时RSet数理也有助于发现无用的引用。参数-XX:+PrintAdaptiveSizePolicy可以开启打印启发式算法决策细节;
    2. 整理堆分区,为混合收集周期识别回收收益高(基于释放空间和暂停目标)的老年代分区集合;
    3. 识别所有空闲分区,即发现无存活对象的分区。该分区可在清除阶段直接回收,无需等待下次收集周期。

    年轻代收集/混合收集周期

    年轻代收集和混合收集周期,是G1回收空间的主要活动。当应用运行开始时,堆内存可用空间还比较大,只会在年轻代满时,触发年轻代收集;随着老年代内存增长,当到达IHOP阈值-XX:InitiatingHeapOccupancyPercent(老年代占整堆比,默认45%)时,G1开始着手准备收集老年代空间。首先经历并发标记周期,识别出高收益的老年代分区,前文已述。但随后G1并不会马上开始一次混合收集,而是让应用线程先运行一段时间,等待触发一次年轻代收集。在这次STW中,G1将保准整理混合收集周期。接着再次让应用线程运行,当接下来的几次年轻代收集时,将会有老年代分区加入到CSet中,即触发混合收集,这些连续多次的混合收集称为混合收集周期(Mixed Collection Cycle)。

    GC工作线程数

    GC工作线程数 -XX:ParallelGCThreads

    JVM可以通过参数-XX:ParallelGCThreads进行指定GC工作的线程数量。参数-XX:ParallelGCThreads默认值并不是固定的,而是根据当前的CPU资源进行计算。如果用户没有指定,且CPU小于等于8,则默认与CPU核数相等;若CPU大于8,则默认JVM会经过计算得到一个小于CPU核数的线程数;当然也可以人工指定与CPU核数相等。

    年轻代收集

    年轻代收集 Young Collection

    每次收集过程中,既有并行执行的活动,也有串行执行的活动,但都可以是多线程的。在并行执行的任务中,如果某个任务过重,会导致其他线程在等待某项任务的处理,需要对这些地方进行优化。

    并行活动

    外部根分区扫描 Ext Root Scanning:此活动对堆外的根(JVM系统目录、VM数据结构、JNI线程句柄、硬件寄存器、全局变量、线程对栈根)进行扫描,发现那些没有加入到暂停收集集合CSet中的对象。如果系统目录(单根)拥有大量加载的类,最终可能其他并行活动结束后,该活动依然没有结束而带来的等待时间。

    更新已记忆集合 Update RS:并发优化线程会对脏卡片的分区进行扫描更新日志缓冲区来更新RSet,但只会处理全局缓冲列表。作为补充,所有被记录但是还没有被优化线程处理的剩余缓冲区,会在该阶段处理,变成已处理缓冲区(Processed Buffers)。为了限制花在更新RSet的时间,可以设置暂停占用百分比-XX:G1RSetUpdatingPauseTimePercent(默认10%,即-XX:MaxGCPauseMills/10)。值得注意的是,如果更新日志缓冲区更新的任务不降低,单纯地减少RSet的更新时间,会导致暂停中被处理的缓冲区减少,将日志缓冲区更新工作推到并发优化线程上,从而增加对Java应用线程资源的争夺。

    RSet扫描 Scan RS:在收集当前CSet之前,考虑到分区外的引用,必须扫描CSet分区的RSet。如果RSet发生粗化,则会增加RSet的扫描时间。开启诊断模式-XX:UnlockDiagnosticVMOptions后,通过参数-XX:+G1SummarizeRSetStats可以确定并发优化线程是否能够及时处理更新日志缓冲区,并提供更多的信息,来帮助为RSet粗化总数提供窗口。参数-XX:G1SummarizeRSetStatsPeriod=n可设置RSet的统计周期,即经历多少此GC后进行一次统计

    代码根扫描 Code Root Scanning:对代码根集合进行扫描,扫描JVM编译后代码Native Method的引用信息(nmethod扫描),进行RSet扫描。事实上,只有CSet分区中的RSet有强代码根时,才会做nmethod扫描,查找对CSet的引用。

    转移和回收 Object Copy:通过选定的CSet以及CSet分区完整的引用集,将执行暂停时间的主要部分:CSet分区存活对象的转移、CSet分区空间的回收。通过工作窃取机制来负载均衡地选定复制对象的线程,并且复制和扫描对象被转移的存活对象将拷贝到每个GC线程分配缓冲区GCLAB。G1会通过计算,预测分区复制所花费的时间,从而调整年轻代的尺寸。

    终止 Termination:完成上述任务后,如果任务队列已空,则工作线程会发起终止要求。如果还有其他线程继续工作,空闲的线程会通过工作窃取机制尝试帮助其他线程处理。而单独执行根分区扫描的线程,如果任务过重,最终会晚于终止。

    GC外部的并行活动 GC Worker Other:该部分并非GC的活动,而是JVM的活动导致占用了GC暂停时间(例如JNI编译)。

    串行活动

    代码根更新 Code Root Fixup:根据转移对象更新代码根。

    代码根清理 Code Root Purge:清理代码根集合表。

    清除全局卡片标记 Clear CT:在任意收集周期会扫描CSet与RSet记录的PRT,扫描时会在全局卡片表中进行标记,防止重复扫描。在收集周期的最后将会清除全局卡片表中的已扫描标志。

    选择下次收集集合 Choose CSet:该部分主要用于并发标记周期后的年轻代收集、以及混合收集中,在这些收集过程中,由于有老年代候选分区的加入,往往需要对下次收集的范围做出界定;但单纯的年轻代收集中,所有收集的分区都会被收集,不存在选择。

    引用处理 Ref Proc:主要针对软引用、弱引用、虚引用、final引用、JNI引用。当Ref Proc占用时间过多时,可选择使用参数-XX:ParallelRefProcEnabled激活多线程引用处理。G1希望应用能小心使用软引用,因为软引用会一直占据内存空间直到空间耗尽时被Full GC回收掉;即使未发生Full GC,软引用对内存的占用,也会导致GC次数的增加。

    引用排队 Ref Enq:此项活动可能会导致RSet的更新,此时会通过记录日志,将关联的卡片标记为脏卡片。

    卡片重新脏化 Redirty Cards:重新脏化卡片。

    回收空闲巨型分区 Humongous Reclaim:G1做了一个优化:通过查看所有根对象以及年轻代分区的RSet,如果确定RSet中巨型对象没有任何引用,则说明G1发现了一个不可达的巨型对象,该对象分区会被回收。

    释放分区 Free CSet:回收CSet分区的所有空间,并加入到空闲分区中。

    其他活动 Other:GC中可能还会经历其他耗时很小的活动,如修复JNI句柄等。

    并发标记周期后的年轻代收集

    并发标记周期后的年轻代收集 Young Collection Following Concurrent Marking Cycle

    当G1发起并发标记周期之后,并不会马上开始混合收集。G1会先等待下一次年轻代收集,然后在该收集阶段中,确定下次混合收集的CSet(Choose CSet)。

    混合收集周期

    混合收集周期 Mixed Collection Cycle

    单次的混合收集与年轻代收集并无二致。根据暂停目标,老年代的分区可能不能一次暂停收集中被处理完,G1会发起连续多次的混合收集,称为混合收集周期(Mixed Collection Cycle)。G1会计算每次加入到CSet中的分区数量、混合收集进行次数,并且在上次的年轻代收集、以及接下来的混合收集中,G1会确定下次加入CSet的分区集(Choose CSet),并且确定是否结束混合收集周期。

    转移失败的担保机制 Full GC

    转移失败的担保机制 Full GC

    转移失败(Evacuation Failure)是指当G1无法在堆空间中申请新的分区时,G1便会触发担保机制,执行一次STW式的、单线程的Full GC。Full GC会对整堆做标记清除和压缩,最后将只包含纯粹的存活对象。参数-XX:G1ReservePercent(默认10%)可以保留空间,来应对晋升模式下的异常情况,最大占用整堆50%,更大也无意义。

    G1在以下场景中会触发Full GC,同时会在日志中记录to-space-exhausted以及Evacuation Failure:

    1. 从年轻代分区拷贝存活对象时,无法找到可用的空闲分区
    2. 从老年代分区转移存活对象时,无法找到可用的空闲分区
    3. 分配巨型对象时在老年代无法找到足够的连续分区

    由于G1的应用场合往往堆内存都比较大,所以Full GC的收集代价非常昂贵,应该避免Full GC的发生。

    第五章 总结

    G1是一款非常优秀的垃圾收集器,不仅适合堆内存大的应用,同时也简化了调优的工作。通过主要的参数初始和最大堆空间、以及最大容忍的GC暂停目标,就能得到不错的性能;同时,我们也看到G1对内存空间的浪费较高,但通过**首先收集尽可能多的垃圾(Garbage First)**的设计原则,可以及时发现过期对象,从而让内存占用处于合理的水平。


    参考资料

    [1] Charlie H, Monica B, Poonam P, Bengt R. Java Performance Companion
    [2] 周志明. 深入理解JVM虚拟机

    展开全文
  • C#如何调用cmd程序执行

    千次阅读 2009-09-07 17:01:00
    以下程序可以用来在C# 中调用已写好的bat,并执行之, 此处假设test.bat 中内容为:@echo offecho we got those parameter %0, %1如下程序则会调用test.bat,并传入参数"first", 然后以timeout=45进行传入。...
  • EF Code First Migrations数据库迁移

    千次阅读 2016-07-25 14:17:43
    1、EF Code First创建数据库 ... 在程序包管理器控制台中执行以下语句,安装EntityFramework。 PM> Install-Package EntityFramework  安装成功后,界面提示如下图:  在新建的Portal控制台应用
  • 本文章包含以下内容: 启用Code First迁移,迁移功能能够让你不必重建数据库就可以更改数据模型并将其部署到生产环境。将应用程序部署到Windows Azure,该步骤是可选的。 启用Code First迁移 当你在开发应用程序...
  • QML官方系列教程——First Steps with QML

    万次阅读 多人点赞 2014-06-04 21:05:44
    假设Qt的二进制文件被安装在系统可执行路径,你可以使用以下的命令显示这个QML文档: qmlscene HelloWorld.qml 你将看到文本"Hello, World"在一个红色矩形的中心。 Handling User Input —— ...
  • 我对 CodeFirst 的理解,与之对应的有 ModelFirst与 DatabaseFirst ,三者各有千秋,依项目实际情况自行选择。 1、开发过程中先行设计数据库并依此在项目中生成 *.dbml 或是 *.edmx 文件的,就是...
  • 参考 ... 注意: 1. 在第一次编译时会报错“The ...--run "${string_prompt}",点击Run,这时输入对应的编译完成的程序名,就可以完成仿真,结果会在console中显示。 贴一下英文的步骤,可以辅助着来看 ...
  • 启用迁移生成并运行迁移自定义迁移数据移动和自定义 SQL迁移到特定版本(包括降级)生成 SQL 脚本在应用程序启动时自动升级(MigrateDatabaseToLatestVersion 初始值设定项)   构建一个初始模型和数据库 在开始...
  • 本文假定你已经对Code First迁移有一定的了解,如果不了解Code First迁移更新数据库可以查看 asp.net mvc Code First模式下迁移更新数据库 这篇文章。 文章涉及的主题如下: 1、创建模型 2、可迁移性 3、添加一个...
  • Model First(模型优先)

    千次阅读 2014-06-17 14:21:29
    必备条件 ...下面我们将建立一个控制台应用程序来说明database first的使用问题 .打开vs .菜单 文件->新建->项目 .在打开的窗口中,左侧选择window,右侧选择console application(控制台应用程序)
  • 在ideaDebug模式下,当程序运行到断点处,点击Stop按钮,程序并未立刻终止,而是会继续执行断点后的代码,以下方式可解决该情况: 浏览器访问,断点到second,此时点击停止 浏览器打印了最后结果,控制台打印的...
  • 《编译原理》-用例题理解-自顶向下语法分析及 FIRST,FOLLOW,SELECT集,LL(1)文法 本笔记是对教材《编译原理》-张晶老师版 做学习笔记。 最近在学《编译原理》,前三章感觉还可以理解,到了第四章就感觉这难度就...
  • Entity Framework 5.0 Code First全面学习

    万次阅读 热门讨论 2013-11-01 17:20:11
    Code First 约定 借助 CodeFirst,可通过使用 C# 或Visual Basic .NET 类来描述模型。模型的基本形状可通过约定来检测。约定是规则集,用于在使用 Code First 时基于类定义自动配置概念模型。约定是在 System.Data....
  • Database First(数据库优先)

    千次阅读 2014-06-17 10:50:04
    要使用database first,我们首先必须有一个数据库来存储我们要使用的数据 我们要使用的数据库服务依赖于你安装的vs版本 .如果你安装的是vs2010,我们将要建立的数据库是基于sql express的(安装vs2010完整
  • 阅读下面的程序,并写出运行结果

    千次阅读 2016-05-26 09:05:32
    * 文件名称:第12周项目:阅读下面的程序,并写出运行结果 * 作者:马康泰 * 完成日期:2016.5.19 * 版本号:v1.0 * * 问题描述:阅读下面的程序,并写出运行结果 * 输入描述: * 程序输出: */ //虚...
  • 前段时间在写一个工具型小软件时需要实现“在触发某个控件的一个事件时,判断某个外部程序(exe)是否已经运行,如果运行则将该程序主窗体打开或窗口置顶,如果没有运行,就通过一个自己指定的路径打开该应用程序”...
  • 这 个进程够cpu把,分别从nice增量值-19到20调用40个该程序,然后查看那些计数器,太帅了,和我想的一样,这时cfs才真正进入正轨,虽然也存 在同一个调度周期调度2次一个进程的,但是明显少了很多,很快这个比例从...
  • 在操作已经创建好的数据库时,若是添加新的实体类或者修改原有数据库上下文,会报如下错误: The model backing the 'StudentDbContext' context ... Consider using Code First Migrations to update the database(ht
  • Code First 创建视图网上也有很多资料,但好像很麻烦,而且亲测好像是无效的方法(可能是我太笨,没搞成功),我摸索出了一种简单有效的方法,这里分享给大家。     EF是Entity Framework(实体框架)的简写,...
  • http://www.linuxsir.org/bbs/printthread.php?t=206356ELF文件格式及程序加载执行过程总汇  这是我这段时间学习elf文件格式搜集的资料,其中的一些重量级文档,比如linkers and loaders ,the executable and...
  • Android应用程序启动过程源代码分析

    万次阅读 多人点赞 2011-08-19 00:58:02
    在Android系统中,应用程序是由Activity组成的,因此,应用程序的启动过程实际上就是应用程序中的默认Activity的启动过程,本文将详细分析应用程序框架层的源代码,了解Android应用程序的启动过程。 在上一篇文章...
  • Code First Migrations更新数据库结构的具体步骤 作者:寒羽枫(cityhunter172)   我对 CodeFirst 的理解,与之对应的有 ModelFirst与 DatabaseFirst ,三者各有千秋,依项目实际情况自行选择。 ...
  • 本文主要通过跟踪一个非常简单的Python程序执行,简单讨论Python实现的基本框架和结构。 要执行Python程序如下,功能非常简单:从1加到10再打印出来 # test program sum = 0 for i in ra
  • C/C++程序编译步骤以及如何生成可执行文件

    万次阅读 多人点赞 2016-07-12 10:32:32
     C/C++语言很多人都比较熟悉,这基本上是每位大学生必学的一门编程语言,通常还都是作为程序设计入门语言学的,并且课程大多安排在大一(反正我是混过来的)。刚上大学,学生们还都很乖,学习也比较认真、用心。...
  • 连接字符串 对于数据库优先模式来说,一般刚接触的开发者...对于这个模式来说,会自动生成一个连接字符串:(类似于以下的形式)(字符串1) 一般而言我们的web开发的字符串应该是这样的:(字符串2) ;Initial
  • 默认情况下,当我们使用Entity Framework Code First 自动创建一个数据库,像我们之前教程中讲的那样,Code First 添加一个table帮我们跟踪数据库结构是否与模型类同步。如果不同步,Entity Framework 将抛出一个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 195,462
精华内容 78,184
关键字:

以下程序执行结果是first