Java垃圾回收精粹分4个部分,本篇是第2部分。在第2部分里介绍了Hotspot中的堆结构、对象分配以及次要回收。

Hotspot中的堆结构

理解不同的收集器的工作方式,是探讨Java堆结构如何支持分代机制的最好的方式。

伊甸区(Eden)的大部分对象都是刚刚被分配的。幸存区(survivor)用来临时存储那些从伊甸区里幸存下来的对象。当我们讨论完次要回收(minor collections)后将描述幸存区的用途。伊甸区和幸存区统称为“年轻代”或 “新生代”存活足够久的对象,将最终移到年老(tenured )区里。

永久代在运行时存储永不销毁对象的区域,比如像类和静态字符串。不幸的是,在许多应用程序中,持续运行的类加载这一常见用途背后隐藏了一个激进的假设:即类是不会被销毁。在Java 7中,本地化的String会从永久代(perm gen)代移动到年老区。从Java 8开始,HotSpot虚拟机中删除了“永久代”。这是题外话,不再过多探讨。大部分其他的商业收集器不使用一个单独的永久代,而是往往把所有长期存活的对象放到年老代里面。

注意:虚拟空间允许收集器调整区块大小,以满足延迟和吞吐量的要求。收集器对每一次的收集做统计,并调整相应区的大小以达设定的目标。

对象分配

为了避免竞争,每个线程都指派了一个“线程本地分配缓冲区”(Thread Local Allocation Buffer,缩写TLAB)用于分配对象。TLAB通过避开单个内存资源竞争问题的方式,使得对象分配的规模可以等同于线程的数量。用TLAB分配对象是一个廉价的操作,只是为对象的大小分配一个指针,在大部分平台上需要约10个指令。Java堆内存的分配比C运行时使用malloc ()分配内存更加廉价。

注意:有鉴于个别对象分配是很廉价的,次要回收(minor collection)发生率必须与对象分配的速率成正比。

当一个TLAB被耗尽后,线程可以简单地从伊甸区请求一个新的。当伊甸区用完后,开始一次次要回收。

大对象(-XX:PretenureSizeThreshold=<n>)在年轻代的分配可能失败,因此必须分配在老年代,比如:大数组。如果Threshold(阈值)的设置低于TLAB大小,那么遇到合适TLAB的对象就不会创建在年老代。G1的新收集器在处理大对象的时候有所不同,将在后面部分单独讨论。

次要回收(Minor Collections )

当伊甸区填满时会触发次要回收。这一过程将所有新生代里存活的对象适当的复制到幸存区(survivor space)和年老区(tenured space)。复制到年老区通常称为晋升(promotion)或者老年化(tenuring)。晋升发生在那些对象足够老(– XX:MaxTenuringThreshold=<n>)或者幸存空间溢出时。

存活对象是指那些应用程序可以访问到的对象;任何不能访问的其他对象,都可以认为是死对象。在次要回收中,存活对象的复制从GC根开始,逐个复制这些可访问的对象到幸存区最后完成。

GC根通常包括应用程序的引用、JVM内部的静态字段的引用以及线程堆栈帧的引用,所有这些构成了应用程序的可访问对象图。

在分代回收中,新生代可访问对象图中的GC根还包括年老代对新生代的任何引用。这些引用也必须进行处理,以确保在新生代里面所有可访问对象在次要回收后仍然是存活的。识别这些跨代引用使用了“卡片表(card table)”。Hotspot 的卡片表是一个bytes数组,其中每个字节用于跟踪相对应年老代里的512byte区域中可能存在的跨代引用。因为引用被存储在堆里,“存储屏障(store barrier)”代码将标记卡片来表示在其相应的512个字节的堆区域,可能存在从年老代到新生代的一个潜在引用。 在回收时卡片表被用于扫描跨代引用,这会为新生代有效地添加一个GC根。因此在次要回收中,一个重要的固定成本是与年老代大小成正比。

在Hotspot中,新生代有两个幸存区:交替的扮演“到达空间(to-space)”和“出发空间(from-space)”的角色。在次要回收刚开始的时候,到达空间的幸存区总是空的,扮演着次要回收中的的目标复制区域的角色。上个次要回收的目标幸存区是这次出发空间的幸存区的一部分。类似的还有伊甸区,里面的存活对象都需要复制。

次要回收的主要消耗在复制对象到幸存区和年老区上。非幸存对象在次要回收中不会被处理。次要回收的工作量直接与存活对象的数量相关,而与新生代的大小无关。Eden的大小每增加一倍,次要回收的总时间可以减少几乎一半。这样可以在内存和吞吐量中找到平衡。Eden的大小翻倍会增加每一次GC周期所占的回收时间。但是如果需要晋升的对象数量和年老代的大小是固定的,那么额外增加的时间会相对较少。

注意:在Hotspot中次要收集是个全局暂停事件。随着我们的堆越来越大,存活对象越来越多,这会变成一个很大的问题。我们已经开始看到在新生代中使用并发收集以减少暂停时间的需要。

主要回收(Major Collections)

“主要回收”是指在旧生代(old gen)上的垃圾收集,以便对象可以从年轻代晋升上来。在大多数应用程序中,绝大部分的程序状态都会在老年代里结束。最多种类的GC算法也是用在老年代上。有一些是整个空间填满时开始压缩,另一些是回收与应用程序并行以防整个空间被填满。

老年代的收集器会尝试预测什么时候需要收集,以避免年轻代的晋升失败。收集器跟踪设置在老年代上的阈值,达到法制就会进行一次回收。如果这个阈值达不到晋升条件,则触发一次“FullGC”。一次FullGC包括晋升所有年轻代中的被收集器追踪的对象,并压缩老年代。晋升失败是个非常昂贵的操作,因为这个周期里所有的状态和晋升对象都必须松开以触发FullGC。

注意:为了避免晋升失败,你需要调整你的填充空间,为老年代提供可以存放晋升后的对象(XX:PromotedPadding=<n>)。

注意:当触发FullGC时,堆可能需要增长。要避免在FullGC时调整堆大小,可以将 –Xms 和 –Xmx 设为相同的值,

与FullGC相比,一次年老代的压缩可能会是应用程序经历最久的全局暂停。压缩的时间和在年老区(tenured space)中存活对象的数量增长呈线性关系。

有时候,年老区的填充率可以通过增大幸存区(survivor spaces)大小和延长晋升到年老区前对象的存活寿命来减少。然而, 增大幸存区大小和延长晋升之前对象在次要收集 (–XX:MaxTenuringThreshold=<n>)中的存活寿命,也会增加次要回收成本和暂停时间,这样的话,幸存区之间的复制成本就会增加。

Java垃圾回收精粹 — Part2的更多相关文章

  1. Java垃圾回收精粹 — Part4

    Java垃圾回收精粹分4个部分,本篇是第4部分.在第4部分里介绍了G1收集器.其他并发收集器以及垃圾收集监控和调优. Garbage First (G1) 收集器 G1 (-XX:+UseG1GC)收 ...

  2. Java垃圾回收精粹 — Part3

    Java垃圾回收精粹分4个部分,本篇是第3部分.在第3部分里介绍了串行收集器.并行收集器以及并发标记清理收集器(CMS). 串行收集器(Serial Collector) 串行收集器是最简单的收集器, ...

  3. Java垃圾回收精粹 — Part1

    Java垃圾回收精粹分4个部分,本篇是第1部分.在第1部分里介绍了权衡点.对象生命周期以及全局暂停事件. 串行.并行.并发.CMS.G1.年轻代(Young Gen).新生代(New Gen).旧生代 ...

  4. 【转载】Java垃圾回收机制

    原文地址:http://www.importnew.com/19085.html Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联 ...

  5. 【转】深入理解 Java 垃圾回收机制

    深入理解 Java 垃圾回收机制   一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...

  6. 深入理解java垃圾回收机制

    深入理解java垃圾回收机制---- 一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...

  7. Java GC系列(2):Java垃圾回收是如何工作的?

    本文由 ImportNew - 伍翀 翻译自 javapapers. 目录 垃圾回收介绍 垃圾回收是如何工作的? 垃圾回收的类别 垃圾回收监视和分析 本教程是为了理解基本的Java垃圾回收以及它是如何 ...

  8. Java GC系列(1):Java垃圾回收简介

    本文由 ImportNew - 好好先生 翻译自 javapapers. Java的内存分配与回收全部由JVM垃圾回收进程自动完成.与C语言不同,Java开发者不需要自己编写代码实现垃圾回收.这是Ja ...

  9. Java垃圾回收介绍(译)

    在Java中,对象内存空间的分配与回收是由JVM中的垃圾回收进程自动完成的.与C语言不同的是,在Java中开发者不需要专门为垃圾回收写代码.这是使Java流行的众多特征之一,也帮助了程序员写出了更好的 ...

随机推荐

  1. ultra-console

    console.__proto__.styleText = function (option) { if (!option) { console.groupCollapsed('请输入option') ...

  2. Eolinker——高级代码模式(JS语法)

    ### 定义遍历与赋值JavaScript 使用关键字 var 来定义变量, 使用等号来为变量赋值:```var a=1;<!--or-->var a;a=1 ``` ### 输出输出函数 ...

  3. day4 递归原理及解析

    递归 递归是一种调用自身的方法,在函数执行过程中重复不断的调用自身的过程,递归的规模每次都要缩小,一般前一步的程序作为后一步的参数.但是必须有递归结束条件. 递归算法是一种直接或者间接地调用自身算法的 ...

  4. 05 java 基础:运算符、程序结构

    赋值运算符 : = 三元运算符 : ? 算术运算符 : +.- .*./.% 自增自减运算符: ++.-- 关系运算符:>.<.==.>=.<=.!= 逻辑运算符 :& ...

  5. spring_150909_hibernate_id_table

    1.新建java工程:spring_150909_hibernate_id_table,如下图所示: 2.建DogPet实体类: package com.spring.model; import ja ...

  6. SaltStack的salt-ssh使用及LAMP状态设计部署(五)

    一.salt-ssh的使用 官方文档:https://docs.saltstack.com/en/2016.11/topics/ssh/index.html (1)安装salt-ssh [root@l ...

  7. poj2078 Matrix(DFS)

    题目链接 http://poj.org/problem?id=2078 题意 输入一个n×n的矩阵,可以对矩阵的每行进行任意次的循环右移操作,行的每一次右移后,计算矩阵中每一列的和的最大值,输出这些最 ...

  8. 自定义mvc验证特性,手机号号段老增加,给自定义一个RegularExpress

    public class PhoneExpressionAttribute: RegularExpressionAttribute, IClientValidatable { public Phone ...

  9. ref:如何在大量jar包中搜索特定字符

    ref:https://www.cnblogs.com/jiangxinnju/p/5137760.html?utm_source=tuicool&utm_medium=referral 如何 ...

  10. Xamarin 2017.9.19更新

     Xamarin 2017.9.19更新   本次更新是添加Xamarin.iOS对iOS 11和Xcode 9的支持.Visual Studio 2017升级到15.3.5获得更新功能.Visual ...