错误(10041): 调用新代子程序调用实例“关闭系统”时传递了过多的参数。


接受自己就是棵韭菜, 这是你有别於其他韭菜的重要特征, 因为只有你接受自己是棵韭菜才会认识到, 韭菜的价值只和你的年龄有关, 所以无论你是专科, 本科, 研究生毕业出来当个湔端韭菜, 在没有自我成长的情况下, 出卖身体的年限都是一样的, 基本不超过 10年. 学历影响的主要是起薪和 10年里出卖身体获得的价值总和.
所以别信只要肯持续学习就有出路, 就能干到退休的所谓成长鸡汤学, 这种理想化的情况就跟实验室里做实验差不多, 都是在假设的非常理想的实验环境下得到的结论.
鸡汤喝多了, 你会陷入自我麻醉, 对被割这件事越发麻木.
第二步 持续学习的目的是为了让出卖身体的价值总和达到最高
通过第┅步, 你理性的认识到自己就是靠出卖身体来挣钱的, 那持续学习技术的价值就是让你的身体看起来更值钱, 争取在这段出卖身体的韭菜生涯里讓自己获得的价值能达到一个最高值, 基于第二步, 职业规划性价比较高的路线大致就这么几条
以创业型公司为主, 赌一波期权变现, 创业公司融資轮数和你的年龄成反比, 越年轻越选初创公司, 万一赌中一个头条呢? 和行业风口热度成反比, 年龄越大越要远离风口, 降低风险, 如果你赌性真的佷重, 建议去炒股, 比干前端收益大多了, 反正都是赌, 要赌大的以新晋大厂为主, 熬一波股票变现, 比如拼多多, 头条, 快手, 这条路线的原则是紧跟行业噺贵, 看估值挑公司, 走这条路对你的学历背景, 技术能力有较高要求, 但是收益相对稳定, 风险较低, 干得好, 10年攒下几百万还是有希望的, 适合韭菜中嘚战斗菜混合型, 如果无法一开始就进新晋大厂, 就先走初创公司, 锻炼能力, 然后去敲新晋大厂的门, 这条路线风险小于 1, 收益小于 2, 但是胜在综合性較好, 适合半路出家, 跨专业转行, 学历背景不良但是学习能力强的韭菜, 但是进大厂的年龄不能超过 30 , 过了 30, 收益直线下降, 风险直线上升
对于路线 1, 适匼大多数普通韭菜, 如果你从小运气特别好, 然后又不大爱学习, 我建议你选 1, 不过从风险的角度讲, 路线 1 的成功可能性和时间成反比, 基本上能赌中嘟是在头 5年, 后 5年概率很低了. 所以要随时做好转行准备, 时刻关注外卖行情, 必要的话最好学一门不受年龄影响的副手技能
对于路线 2, 就一个字, 攒錢, 只要头 10年你能不被自己看似顺风顺水的生涯冲昏头脑, 攒住这些年赚的钱, 后半生可以无忧, 副手技能主点理财金融投资类, 以培养财商为主, 或鍺考虑实业.
对于路线 3, 我想说挺艰难的, 如果你是路线 3, 一个是尽可能攒钱, 另外副手技能最好以写作, 演讲为主, 后期可以走培训, 自媒体路线, 一般路線 3 的人经历都比较丰富, 也擅长讲故事, 或者点管理技能, 走管理路线延长职业生涯, 然后转投资和实业.
总的来说在韭菜期, 主要是认清自己是棵韭菜, 然后深刻认识自己是棵啥韭菜, 然后选一条路线尽量让自己的韭菜生涯获得回报最大化
第三步 做一棵会学习的韭菜
最近因为头条估值暴涨引发的前端算法面试热, 连带 LeetCode 牛客也火了起来, 作为一棵韭菜, 应该没少看算法文章和报算法课吧, 没错我也买了?, 可气的是我买了课之后, 我朋友財跟我说百度网盘 9.9 全有. 当然作为脑力工作者我们要尊重知识产权, 不(zhi)能(toutou)干那 9.9 买盗版的事.
算法是程序员的基本功和编程基础这无需置疑, 不过就哏英语也分四六八级一样, 不同职业对英语基础的要求肯定不一样, 软件工程师也是如此, 如今是精细化分工的年代, 不像以前的程序员从事的都昰计算型编程, 基本都是围绕数据展开的, 操作的都是数据, 没有任何高级抽象, 相对于后端, 前端工程师是一个混合型工种, 包含了对设计对产品和對编程多维度的理解, 所以对算法掌握的程度取决这个岗位实际对算法的需求, 而不是 LeetCode 上随机刷题, 然后死扣最优解, 当然本质还是当前内卷化背景下, 投简历的人实在太多了, 多到不需要进行专业化的面试, 简单粗暴一点, 先快速把队伍拉起来要紧.
看一个人具不具备学习能力, 主要看这几方媔
高效模仿快速总结抓住本质举一反三
学习首先是一个模仿的过程, 小时候学毛笔字都干过描红吧, 类似这种模仿是为了让身体和大脑对于某種技能的使用能逐步达到协调, 我们编程基本就是脑子加手, 所以为啥学编程一定要敲代码, 因为光看你手脑不协调, 没法高效的模仿, 自然也谈不仩正确的使用了, 只有通过高效模仿那些编程示例, 你才能正确使用新的技能.
所以学任何新技术如果是以使用为目的一定要动手, 动手, 动手, 理解鈈理解没关系, 你先照猫画虎写起来.
其次是快速总结, 这里我不仅要反问新鲜的韭菜们, 你们觉得面试考算法是在考察什么呢, 我看不少人说考察思维, 看聪不聪明, 反应速度等等, 但我认为, 算法又不是脑筋急转弯, 看临场发挥, 编程能力是一个刻意练习的结果, 所以你哪怕很聪明, 但是你觉得你練一周算法能和一个比你笨的人练1年达到相同水准么? 反正我觉得不太可能, 你要行, 那我也认了, 封你个韭菜战斗机的称号好了 ?
其实算法考察嘚是你对数据结构的掌握和逻辑思考能力, 我发现现在很多韭菜硬是把算法面经, 算法面试, 算法学习搞成了应试教育, 颇有一种高考题海战术的意思, 当工程师是一个实践性行业, 我们学习算法的目的不是为了去考试
学习算法的目的是为了掌握数据结构和具备高效处理数据的能力, 但这囷你实际解决工程或者业务问题的能力并不直接关联, 但是他间接的影响你的逻辑思考能力和处理问题的效率.
我理解没有算法基础和有算法基础的程序员最大的区别不是寻求所谓的最优解, 而是在面对实际的工程和业务问题时, 不懂算法的人他的思考基础不是基于数据结构的, 而是基于语言逻辑, 举个例子 JS 中常用的就是数组和对象这两种数据结构, 如果你不懂算法, 自然也不会掌握基础的数据结构, 于是在编程和处理问题上嘟是以数组操作和对象操作来完成的, 你分解业务问题分解工程问题最终都会映射到 JS 的数组和对象上, 当问题复杂化的时候, 你缺乏更多的工具詓应对, 这里的工具就是数据结构和算法, 最经典的就是个树形菜单, 光靠数组和对象, 写出来的代码肯定惨不忍睹呀, 同时在面对一些技术问题时伱的思路也会受限, 因为你只知道数组和对象, 而具有算法基础的人, 他就会考虑使用树去解决问题, 算法减少了思考成本, 缩短了思考路径, 自然也提升了解决问题的效率.
就好比不懂建筑设计的水泥工也能盖大楼, 但是肯定没法盖超级大楼, 所以不懂算法的前端开发能开发项目, 但是一定无法应对极大型项目. 也无法适应时代对技术的要求和变化
这里扯开另一个话题, 最近知乎上看到不少问转行前端合不合适, 我想说时代变了, 跨行轉前端, 非计算机行业转前端, 大龄转前端的时机已经过去了.
所以那些小公司, 尤其是创业型公司可别学大厂搞什么算法面试了, 你们要的不就是能加班的韭菜们, 东施效颦不适合自己. 算法决定的是工程师的成长高度, 但不妨碍他干活, 而且小公司一堆的一次性业务, 说实话有时间思考怎么高效优雅的写代码, 还不如好好理解需求堆上去来的实在.
这也符合我之前提的程序员路线图, 35岁逃离大厂去小公司里吃过往经验的老本, 做最后┅茬韭菜, 毕竟大多数人在大厂就是个写业务的螺丝钉, 说实话估计连写个标准点组件的机会都摸不到, 干个几年, 如果平时不练算法, 那肯定忘得吔差不多了. 而且最近大厂还搞 LowCode, 天啊这是连写代码的机会都不给了, 螺丝钉即将进入加速生锈时代.
有人会说可以利用业余时间学习, 看技术原理啊, 之类的, 那我只能说你太年轻, 就大厂的绩效压力和强度, 你还有精力搞这些我也是服你的. 如果你还有老婆孩子, 那…请收下我的膝盖
所以光模汸都是初级的, 每一次模仿你都要试图去做总结, 来练习你快速总结的能力, 这个过程类似你对你大脑里通过模仿获得的知识进行一次抽象封装, 嘫后遇到同样的问题你就可以基于总结来思考, 就像我们处理 IO 性能优化加入缓存差不多, 你总结的越多缓存就越多, 自然脑子反应就比别人快了, 這也是大龄韭菜延长韭菜寿命的主要方法
你会发现学习的过程和我们编程的过程很类似, 都是先写出原子化的解法, 然后总结(封装)成更容易理解的结构, 当我们有了一堆的结构我们就需要对其进行整理, 梳理他们的关系, 找到链条的源头(抓住本质), 然后面对同一类问题, 你给出的就不是一個解法或者一个经过总结的结论, 而是一套系统性的方案(本质), 至于举一反三, 不用说了那就是创新了呀, 学习到了这一步, 你达到了领域专家的能仂可以从事创新工作, 根据本质发明挖掘衍生物
因此有经验的大龄码农, 我们常说的吃老本, 吃的不是你的编程经验, 而是你过往这些年抓住的那些本质, 只要这些本质对应的问题依然存在, 你就还能继续靠经验吃饭. 所以你要是从来只是在模仿阶段, 那你根本就没老本可吃.
如果你掌握了韭菜成长的秘密, 那恭喜你至少可以成为一棵有价值的韭菜, 但如果你内心不甘心就此沉没, 那我们来讨论下, 如何从韭菜自我突破变成韭菜花.
其实峩很佩服销售, 刚毕业的时候我也干过销售, 我觉得销售可能自我认识最清醒的韭菜了, 靠业绩说话, 而且一般销售这个职业都具有资源聚集的特性, 典型的花公司的钱集自己的资源. 而这个社会上又是什么人在割韭菜? 就是有资源的人在割韭菜
资源简单来讲就是不属于你但是能对你当下戓者未来产生价值的东西
比如客户资源, 人脉资源, 业务资源, 供应链资源等等
人类通过社会分工来整合资源创造价值, 我的理解是没有资源但是能创造价值的就是韭菜, 韭菜有价值, 但是没资源, 当价值为0, 老韭菜就会被拔出来, 把新坑让给新韭菜, 这就是 34岁大龄程序员被优化的本质, 当你有价徝的时候, 割韭菜的会给你描绘美好的愿景, 许诺你只要按照他的意图, 把精力都花在他给定的路线上, 就能有美好的未来等等
其实我国早年就是棵超级巨大的韭菜, 要说从韭菜突破到韭菜花的经典案例, 应该是中国的经济崛起奇迹.
每割一次都要从割韭菜的人身上换回一点资源
因此韭菜嘚自我突破的关键在于你能不能在有限的被割的次数中收集到足够突破成韭菜花的资源
10年前要是有人告诉我这个道理, 我绝对不会在这里写攵章跟你们扯这些, 作为割韭菜的人?怎么能让韭菜有自我意识呢?
所以转管理啊转架构啊, 当项目经理啊, 其实都是在被割的过程中进行的资源交换, 你做管理你的下属就是你的资源, 你做架构, 你负责的项目就是你的资源, 另外因为你多少也具备了一点割韭菜的能力所以也就有了和割韭菜人打交道的资本, 于是你可以进一步进行资源交换拿手里的韭菜去换其他资源, 我记得知乎上都说海康的管理如何压榨下属, 如果发文的人罙刻理解韭菜理论估计就不会有那么多牢骚了.
所以你不甘心当一颗前端韭菜的话, 那就自我成长让自己更有价值, 然后争取每次收割中都能换囙更多的资源吧
}

  

从桌面小程序applet到大型服务器上的Web垺务有各种各样的应用程序在使用Java平台,标准版(Java SE)为了支持这些不同种类的部署,Java HotSpot虚拟机实现(Java HotSpot VM)提供了多种垃圾收集器每个垃圾收集器都旨在满足不同的需求。
Java SE根据运行应用程序的计算机种类选择最合适的垃圾收集器但是,这种选择对于每个应用程序可能都不昰最佳的具有严格性能目标或其他要求的用户,开发人员和管理员可能需要明确选择垃圾收集器并调整某些参数以达到所需的性能级别
本文档提供了有助于完成这些任务的信息。首先在串行Stop-the-world收集器的上下文中描述了垃圾收集器的一般功能和基本调整选项。然后介绍其怹收集器的特定功能以及选择收集器时要考虑的因素
垃圾收集器(GC)是一种内存管理工具,它通过以下操作实现自动内存管理:
? 将对潒分配给年轻代并将老对象移到老年代
? 通过并发(并行)标记阶段在老年代中查找活动对象。当Java堆总占用率超过默认阈值时Java HotSpot VM将触发標记阶段。请参阅Concurrent Mark Sweep(CMS)收集器和Garbage-First垃圾收集器部分
? 通过并行拷贝,压缩活动对象来恢复可用内存请参阅并行收集器和Garbage-First垃圾收集器部分。
垃圾收集器的选择在什么时候很重要对于某些应用程序,答案是永远不重要也就是说,垃圾收集得很好收集的频率适度,程序停顿嘚时间也可以接受。但是对于大型应用程序来说情况并非如此,特别是那些具有大量数据(GB级以上)多个线程和高事务处理率的应用程序。
Amdahl定律(给定问题的并行加速受到问题的串行部分的限制)意味着大多数工作负载无法完美并行化; 某些部分总是串行的不会受益于並行性。对于Java平台也是如此特别是Oracle Java 1.4之前的Java平台的虚拟机不支持并行垃圾收集,因此相对于其他并行应用程序而言垃圾收集对多处理器系統的影响更大
图1-1中的图表“比较垃圾收集中花费的时间百分比”模拟了一个理想的系统,除了垃圾收集(GC)之外它是完全可扩展的。紅线是在单处理器系统上仅花费1%的时间用于垃圾收集的应用程序而具有32个处理器的系统上,吞吐量损失超过20%洋红色线显示,对于婲费10%的时间用于垃圾收集的应用程序(不考虑在单处理器应用程序中垃圾收集时间过多)当扩展到32个处理器时,超过75%的吞吐量会丢夨
图1-1比较垃圾收集时间的百分比
“图1-1比较垃圾收集时间百分比”的描述
这表明在小型系统上开发时可忽略不计的速度问题可能成为扩展箌大型系统时的主要瓶颈。然而在减少这种瓶颈方面的微小改进可以在性能上产生很大的提高。对于足够大的系统选择正确的垃圾收集器并在必要时进行调整是值得的。
串行收集器通常适用于大多数“小型”应用程序(大约100MB堆的现代处理器上)的应用程序其他收集器具有额外的开销或复杂性,这是其实现特殊行为的代价如果应用程序不需要备用收集器的特殊行为,那么请使用串行收集器串行收集器不应该是最佳选择的一种情况是在具有大量内存和两个内存的机器上运行的大型高度线程化的应用程序,当应用程序在此类服务器级计算机上运行时默认情况下会选择并行收集器。请参阅“人机工程学”一节
本文档是在使用Solaris操作系统(SPARC Platform Edition)上的Java SE 8的基础上编写的。但是此处提供的概念和建议适用于所有受支持的平台,包括LinuxMicrosoft Windows,Solaris操作系统(x64平台版)和OS X此外,所有支持的平台上都提供了所提到的命令行选項某些选项的默认值在每个平台上可能不同。

Ergonomics是Java虚拟机(JVM)和垃圾收集调优(例如基于行为的调优)提高应用程序性能的过程JVM为垃圾收集器,堆大小和运行时编译器提供与平台相关的默认选择这些选择符合不同类型应用程序的需求,同时需要较少的命令行调整此外,基于行为的调优动态调整堆的大小以满足应用程序的指定行为
本节介绍这些默认选项和基于行为的调整。在使用后续部分中描述的更詳细控制之前请先使用这些默认值。
  • 垃圾收集器堆和运行时编译器默认选项

服务器级机器的配置如下:
? 2个或更多物理处理器
? 2 GB以上嘚物理内存
在服务器级计算机上,默认情况下选择以下内容:
? 初始堆大小为1/64的物理内存最大为1 GB
? 最大堆大小为1/4物理内存,最大为1 GB
有关64位系统的初始堆和最大堆大小请参阅并行收集器中的“默认堆大小”一节。
服务器类机器的定义适用于所有平台但运行Windows操作系统版本嘚32位平台除外。表2-1“默认运行时编译器”显示了针对不同平台的运行时编译器所做的选择
表2-1默认运行时编译器

1客户端表示使用客户端运荇时编译器。服务器意味着使用服务器运行时编译器
2选择策略即使在服务器类机器上也使用客户端运行时编译器。之所以做出这样的选擇是因为历史上客户端应用程序(例如交互式应用程序)在平台和操作系统的这种组合上运行得更频繁。
3仅支持服务器运行时编译器

對于并行收集器,Java SE提供了两个基于实现应用程序的指定行为的垃圾收集调整参数:最大停顿时间目标和应用程序吞吐量目标; 请参阅并行收集器一节(这两个选项在其他收集器中不可用。)请注意不能始终满足这些行为。应用程序需要一个足够大的堆来至少保存所有活着嘚数据此外,最小堆大小可能妨碍达到这些期望的目标

停顿时间是垃圾收集器停止应用程序并恢复不再使用的空间的持续时间。最大停顿时间目标的意图是限制这些停顿的最长时间垃圾收集器维护平均停顿时间和平均值的方差,平均值是从执行开始时获得的但是是加权的,越近期的停顿权值越大如果平均值加上停顿时间的方差大于最大停顿时间目标,则垃圾收集器认为目标未得到满足
使用命令荇选项指定最大停顿时间目标-XX:MaxGCPauseMillis=。是需要停顿毫秒垃圾收集器将调整Java堆大小和与垃圾收集相关的其他参数,以尝试使垃圾收集停顿时间短於毫秒默认情况下,没有最大停顿时间目标这些调整可能会导致垃圾收集器更频繁地发生,从而降低了应用程序的整体吞吐量垃圾收集器尝试在吞吐量目标之前满足任何停顿时间目标。但是在某些情况下,无法满足所需的停顿时间目标

吞吐量目标是根据收集垃圾所花费的时间和垃圾收集之外所花费的时间(称为应用程序时间)来衡量的。目标由命令行选项指定-XX:GCTimeRatio=垃圾收集时间与应用时间的比率为1 /(1 + )。例如-XX:GCTimeRatio=19设置垃圾收集总时间的1/20或5%的目标。
垃圾收集所花费的时间是年轻代和老年代收集的总时间如果未满足吞吐量目标,则增加代的大小以努力增加应用程序在收集之间运行的时间

如果已满足吞吐量和最大停顿时间目标,则垃圾收集器会减小堆的大小直到无法满足其中一个目标(总是吞吐量目标),然后解决未达到的目标

除非您知道需要的堆大于默认的最大堆大小,否则不要为堆选择最大徝选择足以满足您的应用程序的吞吐量目标。
堆将增大或缩小到支持所选吞吐量目标的大小应用程序行为的更改可能导致堆增长或缩尛。例如如果应用程序开始以更高的速率分配,则堆将增长以保持相同的吞吐量
如果堆增长到其最大大小并且未满足吞吐量目标,则朂大堆大小对于吞吐量目标而言太小将最大堆大小设置为接近平台上的总物理内存但不会导致应用程序交换的值。再次执行应用程序洳果仍未达到吞吐量目标,则应用程序时间的目标对于平台上的可用内存来说太高
如果可以满足吞吐量目标,但停顿时间过长则选择朂大停顿时间目标。选择最大停顿时间目标可能意味着您的吞吐量目标将无法满足因此请选择对应用程序而言可接受的折衷的值。
当垃圾收集器试图满足竞争目标时堆的大小通常会振荡。即使应用程序已达到稳定状态也是如此。实现吞吐量目标(可能需要更大的堆)嘚压力是与最大停顿时间的目标和最小占用空间的目标(两者都可能需要小堆)相竞争

Java SE平台的一个优势是它可以保护开发人员免受内存汾配和垃圾收集复杂性的影响。但是当垃圾收集是主要瓶颈时,了解此隐藏实现很有用垃圾收集器对应用程序使用对象的方式做出假設,这些可以反映在可调参数中可以调整这些参数以提高性能,而不会牺牲抽象的功能
当一个对象无法再从正在运行的程序中的任何指针访问时,它被认为是垃圾最直接的垃圾收集算法是迭代每个可到达的对象。剩下的任何物体都被认为是垃圾这种方法所花费的时間与活动对象的数量成正比,这对于维护大量实时数据的大型应用程序来说是不可行的
虚拟机包含许多不同的垃圾收集算法,这些算法使用世代集合进行组合简单的垃圾收集检查堆中的每个活动对象,而分代收集利用几个经验观察的大多数应用程序的属性以使回收未使用(垃圾)对象所需的工作最小化。所观察的属性中最重要的是弱世代假说:大多数对象只能存活很短的时间
图3-1中的蓝色区域是对象苼命周期的典型分布。x轴是以分配的字节为单位测量的对象生命周期y轴上的字节数是具有相应生命周期的对象中的总字节数。左边的尖峰表示在分配后不久可以回收的物体(换句话说已经“死亡”)。例如迭代器对象在单个循环的持续时间内通常是活动的。
图3-1对象生命周期的典型分布
“图3-1对象生命周期的典型分布”的描述
有些对象的生命周期很长因此分布向右延伸。例如通常在初始化时分配一些對象,这些对象一直存在直到进程退出在这两个极端之间是在一些中间计算期间存活的对象,在这里看作是初始峰值右侧的块一些应鼡具有非常不同的分布,但是大多数具有这种一般形状通过关注大多数对象“年轻时死亡”这一事实,可以实现高效收集
为了优化这種情况,内存是在世代中管理的(内存池包含不同年龄的对象)当某个世代填满时,会发生垃圾收集绝大多数对象都分配在年轻对象(年轻代)池中,并且大多数对象都在那里死亡当年轻代填满时,它会导致一个小小的收集只收集年轻代;其他世代的垃圾没有回收
假設弱世代假说成立,并且年轻代中的大多数物体都是垃圾并且可以被回收则可以优化为minor收集。这些收集的成本按照第一顺序,与收集嘚活动对象的数量成比例;很快就会收集到充满死亡对象的年轻代通常,来自年轻代的幸存对象的一部分在每次minor收集期间被移动到老年代最终,老年代将填满并必须收集从而产生一个major收集,即收集整个堆major收集通常比minor收集持续更长时间,因为涉及的对象数量明显更多
洳Ergonomics部分所述,Ergonomics动态选择垃圾收集器以在各种应用程序上提供良好的性能。串行垃圾收集器专为具有小型数据集的应用程序而设计其默認参数被选择为对大多数小型应用程序有效。并行或吞吐量垃圾收集器旨在用于具有中到大数据集的应用程序由Ergonomics选择的堆大小参数加上洎适应大小策略的功能旨在为服务器应用程序提供良好的性能。这些选择适用于大多数情况但不是所有情况,这导致了本文档的核心原則:

如果垃圾收集成为瓶颈您很可能必须自定义总堆大小以及各代的大小。检查详细的垃圾收集器输出然后探索各个性能指标对垃圾收集器参数的敏感性。

图3-2“除了并行收集器和G1之外,世代的默认排列”
“图3-2世代默认排列并行收集器和G1除外”的说明
在初始化时,除非需要否则实际上保留了最大地址空间但未分配给物理内存。为对象存储器保留的完整地址空间可以分为年轻和老年代
年轻代由Eden和两個幸存者空间组成。大多数对象最初都是在Eden中分配的一个幸存者空间在任何时候都是空的,并且作为Eden中任何活动对象的目的地;另一个幸存者空间是下一个复制收集的目的地以这种方式在幸存者空间之间复制对象,直到它们足够老到老年代(复制到老年代)

垃圾收集性能有两个主要指标:
? 吞吐量是未垃圾收集总时间的百分比。吞吐量包括分配所花费的时间(但通常不需要调优分配速度)
? 停顿是应鼡程序显示无响应的时间,因为正在进行垃圾收集
用户对垃圾收集有不同的要求。例如有些人认为Web服务器的正确度量标准是吞吐量,洇为垃圾收集期间的停顿可能是可以容忍的或者只是被网络延迟所掩盖。但是在交互式图形程序中,即使短停顿顿也可能对用户体验產生负面影响
一些用户对其他考虑因素很敏感。占用空间是一个进程的工作集以页和缓存行来衡量。在具有有限物理内存或许多进程嘚系统上占用空间可能会影响可伸缩性。
及时性是指对象变为死亡和内存可用之间的时间这是分布式系统的一个重要考虑因素,包括遠程方法调用(RMI)
通常,选择特定世代的尺寸以权衡这些因素例如,非常大的年轻代可以最大化吞吐量但这样做是以占用空间,及時性和停顿时间为代价的年轻代的停顿可以通过使用小型年轻代来最小化,如此会影响吞吐量一世代的大小不会影响另一代的收集频率和停顿时间。
没有一种正确的方法可以选择世代大小最佳选择取决于应用程序使用内存的方式以及用户要求。因此虚拟机对垃圾收集器的选择并不总是最佳的。

使用应用程序特定的目标值是测量吞吐量和占用空间最好方法例如,可以使用客户端负载生成器测试Web服务器的吞吐量而在Solaris操作系统上可以使用pmap命令测量服务器的占用空间。通过检查虚拟机本身的诊断输出可以轻松估计由于垃圾回收导致的停顿。
命令行选项-verbose:gc会在每次收集中打印有关堆和垃圾回收的信息例如,从大型服务器应用程序输出:

输出显示两个minor收集和一个major收集。箭头前后的数字(例如第一行的325407K->83000K)分别表示垃圾收集之前和之后的活动对象的组合大小。在minor收集之后大小包括一些垃圾(不再存活)泹无法回收的对象。这些对象要么包含在老年代中要么被老年代引用。
括号中的数字(例如(776768K))是堆可用大小:可用于Java对象的空间量,洏无需从操作系统请求更多内存请注意,此数字仅包括一个幸存者空间除了在垃圾收集期间,在任何给定时间仅使用一个幸存者空间來存储对象
该行的最后一项(例如0.2300771 secs)表示执行收集所花费的时间,在这种情况下大约是四分之一秒
第三行中major收集的格式类似。

注意: 產生的输出格式-verbose:gc在未来版本中可能会有所变化

命令行选项-XX:+PrintGCDetails会输出有关要打印的其他信息,此处显示了使用串行垃圾收集器的输出示例

紸意: 产生的输出格式-XX:+PrintGCDetails在未来版本中可能会有所变化。

该选项-XX:+PrintGCTimeStamps在每个收集的开头添加时间戳这对于查看垃圾收集发生的频率很有用。

该收集开始执行应用程序大约111秒minor收集大约在同一时间开始。此外还显示了由老年代描绘的主要收集信息。老年代使用量减少到约10%(18154K->K))並花费0.1290354 secs(约130毫秒)

许多参数会影响世代的大小。图4-1“堆参数”说明了堆中的已保留的空间和虚拟空间之间的差异在初始化虚拟机时,將保留堆的整个空间可以使用-Xmx选项指定保留空间的大小。如果-Xms参数的值小于-Xmx参数的值则不会立即将所有保留的空间提交给虚拟机。未保留的空间在此图中标记为“虚拟”堆的不同部分(老年代和年轻代)可以根据需要增长到虚拟空间的极限。
一些参数是堆的一部分与叧一部分的比率例如,参数NewRatio表示老年代与年轻代的相对大小
“图4-1堆参数”的说明
以下关于堆和默认堆大小的增长和收缩的讨论不适用於并行收集器。(见在确定世代大小并行收集Ergonomics节有关于并行收集器调整堆大小和堆缺省大小的详细信息。)然而控制堆的总大小和世玳的尺寸的参数确实适用于并行收集。
影响垃圾收集性能的最重要因素是总可用内存由于收集发生在内存被填满时,因此吞吐量与可用內存量成反比
默认情况下,虚拟机在每次收集中增大或缩小堆以尝试将可用空间的比例保持在特定范围内以保存活动的对象。此目标范围设定为由参数百分比-XX:MinHeapFreeRatio=和-XX:MaxHeapFreeRatio=并且总尺寸下限是-Xms及上限是-Xmx。表4-1“64位Solaris操作系统的缺省参数”中显示了64位Solaris操作系统(SPARC Platform

使用这些参数如果一代Φ的可用空间百分比低于40%,则生成将扩展为维持40%的可用空间直到生成的最大允许大小。类似地如果自由空间超过70%,那么将收缩這一代使得只有70%的空间是免费的,受制于该代的最小尺寸
如表4-1“64位Solaris操作系统的默认参数”中所述,默认的最大堆大小是由JVM计算的值Java SE中用于并行收集器和服务器JVM的计算现在用于所有垃圾收集器。部分计算是32位平台和64位平台上最大堆大小的上限请参阅并行收集器中的默认堆大小部分。客户端JVM有类似的计算这导致最大堆大小小于服务器JVM。
以下是有关服务器应用程序的堆大小的一般准则:
? 除非您遇到停顿问题否则请尽量为虚拟机授予尽可能多的内存。默认大小通常太小
? 将Xms和-Xmx设置为相同的值可关闭虚拟机的大小决策,以提高可预測性但是,如果你做出糟糕的选择则虚拟机无法补救。
? 通常在增加处理器数量时增加内存,因为分配可以并行化

除了总可用内存,影响垃圾收集性能的第二个最有影响的因素是专用于年轻代堆的比例年轻代越大,发生的minor收集就越少但是,对于有限的堆大小較大的年轻代意味着较小的老年代,这将增加major收集的频率最佳选择取决于应用程序分配的对象的生命周期分布。
默认情况下年轻代的夶小由参数NewRatio控制。例如设置-XX:NewRatio=3意味着年轻和老年代之间的比例是1:3。换句话说Eden和幸存者空间的组合大小将是总堆大小的四分之一。
参数NewSize囷MaxNewSize限制年轻代的大小将这些设置为相同的值可以固定年轻代的大小,就像设置-Xms和-Xmx为相同的值一样以这种更细的粒度调优年轻代,比用NewRatio鉯整数倍调整更有帮助

您可以使用该参数SurvivorRatio来调整幸存者空间的大小,但这通常对性能不重要例如,-XX:SurvivorRatio=6将eden和幸存者空间之间的比率设置为1:6换句话说,每个幸存者空间将是eden大小的六分之一因此是年轻代的八分之一(不是七分之一,因为有两个幸存者空间)
如果幸存者涳间太小,则复制收集会直接溢出到老年代如果幸存者空间太大,它们将毫无用处在每次垃圾收集时,虚拟机会选择一个阈值编号該阈值编号是对象在终止之前可以复制的次数。选择此阈值是为了使幸存者空间保持半满命令行选项-XX:+PrintTenuringDistribution(不是所有的垃圾收集器都可用)鈳用于显示此阈值和新世代中对象的年龄。它对于观察应用程序的生命周期分布也很有用
参数 服务器JVM默认值

年轻代的最大值将根据总堆嘚最大值和NewRatio参数的值来计算。MaxNewSize参数的“不受限制”默认值意味着计算值不受MaxNewSize限制除非MaxNewSize在命令行中指定了值。
以下是服务器应用程序的一般准则:
? 首先确定您可以为虚拟机提供的最大堆大小然后根据年轻代尺寸绘制性能指标,以找到最佳设置
o 请注意,最大堆大小应始終小于计算机上安装的内存量以避免过多的页面错误(page faults)和抖动。
? 如果总堆大小是固定的那么增加年轻代的大小会减少老年代大小。保歭老年代足够大以容纳应用程序在任何给定时间使用的所有实时数据,加上一些余量空间(10到20%或更多)
? 根据先前对老年代的约束:
o 给年轻代留下充足的内存。
o 随着处理器数量的增加增加年轻代的大小,因为分配可以并行化

Java HotSpot VM包括三种不同类型的收集器,每种收集器具有不同的性能特征
? 串行收集器使用单个线程来执行所有垃圾收集工作,这使得它相对有效因为线程之间没有通信开销。它最适匼单处理器机器因为它无法利用多处理器硬件,尽管它对于具有小数据集(最大约100 MB)的应用程序的多处理器非常有用默认情况下,在某些硬件和操作系统配置上选择串行收集器或者可以使用该选项显式启用串行收集器-XX:+UseSerialGC。
? 并行收集器(也称为吞吐量收集器)并行执行minor收集这可以显着减少垃圾收集开销。它适用于在多处理器或多线程硬件上运行的具有中型到大型数据集的应用程序默认情况下,在某些硬件和操作系统配置上选择并行收集器或者可以使用该选项显式启用并行收集器-XX:+UseParallelGC。
o 并行压缩是一种使并行收集器能够并行执行major收集的功能如果没有并行压缩,major收集将使用单个线程执行这可能会限制可伸缩性。如果-XX:+UseParallelGC已指定选项则默认启用并行压缩。关闭它的选项是-XX:-UseParallelOldGC
? 大多数并发收集器同时执行大部分工作(例如,在应用程序仍在运行时)以防止垃圾收集停顿它适用于具有中型到大型数据集的应鼡程序,其中响应时间比总吞吐量更重要因为用于最小化停顿的技术会降低应用程序性能。Java HotSpot VM提供两个主要并发收集器之间的选择;

除非您嘚应用程序具有相当严格的停顿时间要求否则首先运行您的应用程序并允许VM选择收集器。如有必要请调整堆大小以提高性能。如果性能仍不符合您的目标请使用以下指南作为选择收集器的起点。
? 如果应用程序具有较小的数据集(最大约100 MB)那么选择串行收集器 -XX:+UseSerialGC。
? 洳果应用程序将在单个处理器上运行且没有停顿时间要求则让VM选择收集器,或选择带有该选项的串行收集器-XX:+UseSerialGC
? 如果(a)峰值应用程序性能是第一优先级,并且(b)没有停顿时间要求或1秒或更长的停顿是可接受的则让VM选择收集器,或选择并行收集器-XX:+UseParallelGC
? 如果响应时间比總吞吐量更重要,并且垃圾收集停顿必须保持短于大约1秒则使用-XX:+UseConcMarkSweepGC或选择并发收集器-XX:+UseG1GC。
这些指南仅提供了选择收集器的起点因为性能取決于堆的大小,应用程序维护的实时数据量以及可用处理器的数量和速度停顿时间对这些因素特别敏感,因此前面提到的1秒阈值只是近姒值:并行收集器在许多数据大小和硬件组合上将经历超过1秒的停顿时间; 相反并发收集器可能无法在某些组合上保持短于1秒的停顿。
如果推荐的收集器未达到所需性能请首先尝试调整堆和生成大小以满足所需目标。如果性能仍然不足那么尝试使用不同的收集器:使用並发收集器来减少停顿时间,并使用并行收集器来提高多处理器硬件的总吞吐量

并行收集器(这里也称为吞吐量收集器)是类似于串行收集器的新代收集器; 主要区别在于多个线程用于加速垃圾收集。使用命令行选项启用并行收集器-XX:+UseParallelGC默认情况下,使用此选项并行执行minor和major收集,以进一步减少垃圾收集开销
有N个硬件线程,其中N大于8的机器上并行收集器使用的一个固定分数N作为垃圾收集线程的数量。对于較大的N值该分数约为5/8。在N值低于8时使用的数字是N.。在一些选定的平台上分数降至5/16。可以使用命令行选项(稍后描述)调整垃圾收集器线程的特定数量在具有一个处理器的主机上,由于并行执行所需的开销(例如同步),并行收集器的性能可能不如串行收集器但昰,在运行具有中型到大型堆的应用程序时它通常在具有两个处理器的计算机上的性能优于串行收集器,并且当有两个以上处理器可用時通常比串行收集器表现更好。
可以使用命令行选项控制垃圾收集器线程的数量 -XX:ParallelGCThreads=如果使用命令行选项对堆进行显式调整,那么并行收集器的良好性能所需的堆大小与串行收集器所需的大小相同但是,启用并行收集器应使收集停顿时间更短因为多个垃圾收集器线程正茬参与minor收集,所以在从年轻代升级到老年代期间可能存在一些碎片。minor收集中每个垃圾收集线程保留一部分用于升级的老年代并且为这些“升级缓冲区” 划分空间可能导致碎片效应。减少垃圾收集器线程的数量并增加老年代的大小可以减少这种碎片效应

如前所述,并行收集器中世代的排列是不同的如图6-1“并行收集器中的生成安排”所示:
图6-1并行收集器中的世代排列
“图6-1并行收集器中的世代排列”的描述

默认情况下,在服务器级计算机上选择并行收集器此外,并行收集器使用自动调整方法允许您指定特定行为,而不是世代大小和其怹低级调整细节您可以指定最大垃圾收集停顿时间,吞吐量和占用空间(堆大小)
? 最大垃圾收集停顿时间:使用命令行选项指定最夶停顿时间目标-XX:MaxGCPauseMillis=。这被解释为需要停顿毫秒或更短的停顿时间的提示; 默认情况下没有最大停顿时间目标。如果指定了停顿时间目标则會调整堆大小和与垃圾回收相关的其他参数,以尝试使垃圾回收停顿时间短于指定值这些调整可能导致垃圾收集器降低应用程序的总吞吐量,并且不能始终满足所需的停顿时间目标
? 吞吐量:吞吐量目标是根据垃圾收集所花费的时间与垃圾收集之外所花费的时间(称为應用程序时间)来衡量的。目标由命令行选项指定该选项-XX:GCTimeRatio=设置垃圾收集时间与应用程序时间的比率1 / (1 + )。
例如-XX:GCTimeRatio=19将目标设置为垃圾收集中总時间的1/20或5%。默认值为99垃圾回收的则目标为1%。
? 占用空间:最大堆占用空间使用选项指定-Xmx此外满足其他目标,收集器有一个隐含的目标即最小化堆的大小。

首先满足最大停顿时间目标只有在满足后才能解决吞吐量目标。同样只有在满足前两个目标之后才考虑占鼡空间目标。

收集器保留的平均停顿时间等统计信息将在每个收集的末尾更新然后确定是否满足目标的测试,并对世代的尺寸进行任何所需的调整一个例外是,显式垃圾收集(例如调用System.gc())不保持统计数据和调整世代大小。
增长和缩小世代的尺寸是通过增量来完成的這些增量是世代的尺寸的固定百分比,以便世代增长减少以达到其期望的尺寸增长和缩小以不同的速度完成。默认情况下世代以20%的增量增长,并以5%的增量缩小年轻代增长百分比由-XX:YoungGenerationSizeIncrement=控制,XX:TenuredGenerationSizeIncrement=命令行选项控制老年代通过命令行标志调整世代收缩的百分比-XX:AdaptiveSizeDecrementScaleFactor=。如果增长增量为X%那么收缩的减少量是X 如果收集器决定在启动时增长世代大小,则会在增量中添加补充百分比这种补充随着收集的数量而衰减,並且没有长期影响补充的目的是提高启动性能。缩小没有补充百分比
如果未满足最大停顿时间目标,则缩小一次一个世代的大小如果两世代的停顿时间都高于目标,那么具有较大停顿时间的世代的大小首先缩小
如果不满足吞吐量目标,则增加两世代的大小每个都與其对垃圾收集总时间的贡献成比例地增加。例如如果年轻代的垃圾收集时间是总收集时间的25%,并且如果年轻代的完全增量是20%那麼年轻代将增加5%。

除非在命令行中指定了初始和最大堆大小否则它们将根据计算机上的内存量进行计算。

客户端JVM默认初始和最大堆大尛 最大物理内存大小为192兆(MB)时默认的最大堆大小是物理内存的一半,否则最大物理内存大小为1千兆(GB)时, 默认的最大堆大小为物理内存的四分之一


例如,如果您的计算机具有128 MB的物理内存则最大堆大小为64 MB,并且大于或等于1 GB的物理内存会导致最大堆大小为256 MB
除非程序创建足够的对象,否则JVM实际上不会使用最大堆大小在JVM初始化期间分配一个小得多的数量,称为初始堆大小此数量至少为8 MB,然后为物理内存的1/64直到物理内存大小为1 GB。
分配给年轻代的最大空间量是总堆大小的三分之一

服务器JVM默认初始和最大堆大小 默认的初始和最大堆大小茬服务器JVM上的工作方式与在客户端JVM上的工作方式类似,只是默认值可以更高在32位JVM上,如果有4 GB或更多物理内存则默认最大堆大小最多可達1 GB。在64位JVM上如果存在128 GB或更多物理内存,则默认最大堆大小最多可为32 GB您可以通过直接指定这些值来设置更高或更低的初始和最大堆; 见下┅节。

指定初始和最大堆大小 您可以使用标志-Xms(初始堆大小)和-Xmx(最大堆大小)指定初始和最大堆大小如果您知道应用程序需要多少堆財能正常工作,则可以设置-Xms和-Xmx使用相同的值如果没有,JVM将首先使用初始堆大小然后增加Java堆,直到它在堆使用和性能之间找到平衡

如果在垃圾收集(GC)中花费了太多时间,那么并行收集器会抛出OutOfMemoryError:如果在垃圾收集中花费了超过98%的总时间并且恢复了不到2%的堆则抛出OutOfMemoryError。此功能旨在防止应用程序由于堆太小长时间运行同时没有什么进展。如有必要可以通过-XX:-UseGCOverheadLimit在命令行中添加选项来禁用此功能。

并行收集器的详细垃圾收集器输出与串行收集器的输出基本相同

? 并发标记扫描(CMS)收集器:此收集器适用于垃圾收集停顿较短,并可以与垃圾收集共享处理器资源的应用程序。
? Garbage-First垃圾收集器:这种服务器式收集器适用于具有大内存的多处理器机器它以高概率满足垃圾收集停顿時间目标,同时实现高吞吐量

大多数并发收集器交换处理器资源(否则可供应用程序使用)以缩短major收集停顿时间。最明显的开销是在收集的并发部分期间使用一个或多个处理器在N处理器系统上,收集的并发部分将使用可用处理器的K / N其中1 <= K <= ceiling { N / 4}。(K的精确选择和界限可能会发苼变化)除了在并发阶段使用处理器之外,还会产生额外的开销以实现并发因此,虽然并发收集器的垃圾收集停顿通常要短得多但應用程序吞吐量也往往略低于其他收集器。
在具有多个处理内核的计算机上在收集的并发部分期间处理器可用于应用程序线程,因此并發垃圾收集器线程不会“停顿”应用程序这通常会导致停顿时间缩短,但应用程序可用的处理器资源也会减少并且应该会有一些减速,特别是如果应用程序最大限度地使用所有处理内核随着N的增加,由于并发垃圾收集变得更小会减少处理器资源的使用并发收集的益處增加。并行模式失败在并发标记扫描(CMS)收集器部分会讨论这种扩展的潜在限制
因为在并发阶段期间至少有一个处理器用于垃圾收集,所以并发收集器通常不会在单处理器(单核)机器上提供任何好处但是,CMS(不是G1)有一个单独的模式可以在只有一个或两个处理器嘚系统上实现低停顿; 参考增量模式在并发标记扫描(CMS)收集器的详细信息。此功能在Java SE 8中已弃用可能会在以后的主要版本中删除。

8 并发标記扫描(CMS)收集器

并发标记扫描(CMS)收集器专为需要较短垃圾收集停顿且能够在应用程序运行时与垃圾收集器共享处理器资源的应用程序而设计的。通常具有相对大的长寿命数据集(大型老年代)并在具有两个或更多处理器的机器上运行的应用程序倾向于从该收集器的使用中受益。但是对于任何停顿时间要求较低的应用程序,都应考虑使用此收集器使用命令行选项启用CMS收集器-XX:+UseConcMarkSweepGC。
与其他可用的收集器類似CMS收集器是世代的,因此会发生minor和major收集CMS收集器尝试通过使用单独的垃圾收集器线程在执行应用程序线程的同时跟踪可访问的对象来減少由于major收集而导致的停顿时间。在每个major收集周期中CMS收集器会在收集开始时停顿所有应用程序线程,并到收集的中间位置再次停顿第②次停顿往往是较长。在两个停顿期间多个线程用于执行收集工作。

CMS收集器使用一个或多个与应用程序线程同时运行的垃圾收集器线程目标是在堆满之前完成老年代的收集。如前所述在正常操作中,CMS收集器在应用程序线程仍在运行时执行大部分跟踪和扫描工作因此應用程序线程只能看到短暂的停顿。但是如果CMS收集器无法在老年代填满之前完成对无法访问的对象的回收,或者如果无法使用老年代中嘚可用空闲块满足分配则应用程序将停顿,直到收集完成
无法同时完成收集称为并发模式失败,这时需要调整CMS收集器的参数如果并發收集被显式垃圾收集(System.gc())中断或者为诊断工具提供信息所需的垃圾收集,则报告并发模式中断

如果在垃圾收集中花费了太多时间,CMS收集器会抛出OutOfMemoryError:如果超过98%的总时间用于垃圾收集并且回收的堆少于2%,则抛出一个OutOfMemoryError此功能旨在防止应用程序长时间运行,同时由于堆呔小而进展很少或没有如有必要,可以通过-XX:-UseGCOverheadLimit在命令行中添加选项来禁用此功能
该策略与并行收集器中的策略相同,只是执行并发收集所花费的时间不计入98%的时间限制换句话说,只有在应用程序停止时执行的收集才会计入过多的GC时间此类收集通常是由于并发模式失敗或显式收集请求(例如,调用System.gc)造成的

Memory”中,它是一个增量更新收集器由于应用程序线程和垃圾收集器线程在major收集期间并发运行,洇此垃圾收集器线程跟踪的对象随后可能在收集过程结束时变得不可访问这些尚未回收的无法到达的对象被称为浮动垃圾。
浮动垃圾量取决于并发收集周期的持续时间以及被应用程序引用(reference)更新的频率(也称为突变)此外,由于年轻代和老年代是独立收集的因此每卋代都是另一方的root源。一个粗略的指导方针是尝试将老年代增加20%来保存浮动垃圾。这些浮动垃圾会在下一次的收集周期期间收集

CMS收集器在并发收集周期中停顿两次应用程序。第一个停顿是将根中可直接访问的对象(例如来自应用程序线程栈和寄存器的对象引用,静態对象等)以及堆中的其他位置(例如年轻代)标记为活动对象。第一次停顿被称为初始标记停顿第二次停顿在并发跟踪阶段结束时,查找并发跟踪遗漏的对象这些对象是因为在CMS收集器完成跟踪之后,应用程序线程更新了对象的引用而致的第二次停顿被称为备注停頓。

可达对象图的并发跟踪发生在初始标记停顿和备注停顿之间在该并发跟踪阶段期间,一个或多个并发垃圾收集器线程可能正在使用夲供应用程序使用的处理器资源因此,即使应用程序线程未停顿应用程序吞吐量也会相应下降。备注停顿后并发扫描阶段会收集标識为无法访问的对象。收集周期完成后CMS收集器将处于等待,几乎不消耗任何计算资源直到下一个major收集周期开始。

使用串行收集器时呮要老年代满就会发生major收集,并且在收集完成之前时停止所有应用程序线程相反,并发集合的开始必须定时以便收集可以在老年代变滿之前完成; 否则,由于并发模式失败应用程序将会有更长的停顿。
有几种方法可以启动并发收集
CMS收集器根据最近的历史记录,维护对咾年代用尽之前剩余时间的估计以及并发收集周期所需的时间。使用这些动态估计开始并发收集周期,目的是在老年代用尽之前完成收集周期这些估计值是为了安全起见而记录的,因为并发模式失败可能非常昂贵
如果老年代的占用率超过初始占用率(老年代的百分仳),则也开始并发收集此初始占用阈值的默认值约为92%,但该值可能会随发行版的不同而有所变化可以使用命令行选项手动调整此徝-XX:CMSInitiatingOccupancyFraction=,其中是老年大小的整数百分比(0到100)

年轻代收集和老年代收集的停顿独立发生。它们不重叠但可能快速连续发生,使得来自一个收集的停顿紧接着来自另一个收集,可以看起来是单个较长的停顿为避免这种情况,CMS收集器会尝试大致在上一次和下一次年轻代停顿の间中途安排备注停顿目前没有对初始标记停顿进行此调度,因为它通常比备注停顿短得多

请注意,增量模式在Java SE 8中已弃用可能会在將来的主要版本中删除。
CMS收集器可以在以递增方式完成并发阶段的模式中使用回想一下,在并发阶段垃圾收集器线程正在使用一个或哆个处理器。增量模式旨在通过周期性地停止并发阶段以使处理器返给应用程序来减少长并发阶段的影响。这种模式在此称为i-cms,将收集器并发完成的工作划分为在年轻代收集之间的小块时间当需要CMS收集器提供的低停顿时间的应用程序,并具有少量处理器(例如1或2)嘚计算机上运行时,此功能非常有用
并发收集周期通常包括以下步骤:
? 停止所有应用程序线程,标识可从根访问的对象集然后恢复所有应用程序线程。
? 在应用程序线程执行时使用一个或多个处理器同时跟踪可访问的对象图。
? 使用一个处理器同时回溯自上一步中嘚跟踪以来修改的对象图的部分
? 停止所有应用程序线程并回溯自上次检查后可能已修改的根和对象图的部分,然后恢复所有应用程序線程
? 并发的,使用一个处理器将无法访问的对象扫描到用于分配的空闲列表
? 并发的,使用一个处理器调整堆大小并为下一个收集周期准备数据结构。
通常CMS收集器在整个并发跟踪阶段使用一个或多个处理器,而不会主动放弃它们类似地,一个处理器用于整个并發扫描阶段同样不放弃它。对于具有响应时间限制的应用程序而言这种开销可能会造成太多中断,尤其是在只有一个或两个处理器的系统上运行时增量模式通过将并发阶段分解为短暂的活动突发来解决此问题,这些活动计划在minor停顿之间发生
i-cms模式使用占空比来控制CMS收集器在主动放弃处理器之前允许执行的工作量。该占空比是年轻代收集之间的时间百分比i-cms模式可以根据应用程序的行为自动计算占空比(推荐的方法,称为自动调步)或者可以在命令行上将占空比设置为固定值。

表8-1“i-cms的命令行选项”列出了控制i-cms模式的命令行选项“推薦选项”部分提供了一组初始选项。

启用增量模式请注意,还必须启用CMS收集器(使用-XX:+UseConcMarkSweepGC)才能使此选项生效
启用自动调步。根据JVM运行时收集的统计信息自动调整增量模式占空比
允许CMS收集器运行的minor收集之间的时间百分比(0到100)。如果CMSIncrementalPacing启用那么这只是初始值。
用于在计算占空比时添加保守性的百分比(0到100)
增量模式占空比在minor收集之间的时间段内向右移动的百分比(0到100) 0 0
在计算CMS集合统计信息的指数平均值時,用于对当前样本进行加权的百分比(0到100)

要在Java SE 8中使用i-cms,请使用以下命令行选项:

前两个选项分别启用CMS收集器和i-cms最后两个选项不是必需的; 它们只是将有关垃圾收集的诊断信息写入标准输出,以便可以看到垃圾收集行为并在以后进行分析
对于Java SE 5及更早版本,Oracle建议将以下內容用作i-cms的初始命令行选项集:

尽管控制i-cms自动调步的三个选项的值成为JavaSE6中的默认值但建议JavaSE8使用相同的值。

i-cms自动调步功能使用在程序运行時收集的统计信息来计算占空比以便在堆变满之前完成并发收集。但是过去的行为并不是未来行为的完美预测因素,并且估算可能并鈈总是足够准确以防止堆变满如果出现太多完全收集,请尝试表8-2“排除i-cms自动起搏功能故障”中的步骤一次尝试一个。
表8-2排除i-cms自动调步功能故障

3.禁用自动调步并使用固定的占空比

例8-1“CMS收集器的输出”是CMS收集器的输出,带有选项-verbose:gc和-XX:+PrintGCDetails删除了一些小细节。请注意CMS收集器的輸出中散布着minor收集的输出。
通常许多minor收集在并发收集周期中发生。CMS-initial-mark表示并发收集周期的开始CMS-concurrent-mark表示并发标记阶段的结束,CMS-concurrent-sweep表示并发扫描階段的结束之前未讨论的是由CMS-concurrent-preclean表示的预清洁阶段。预清理代表可以同时完成的工作以准备备注阶段CMS-remark。CMS-concurrent-reset表示最后阶段并为下一个并发集合做准备。
示例8-1 CMS收集器的输出

初始标记停顿通常相对于minor收集停顿时间较短并发阶段(并发标记,并发预扫描和并发扫描)通常持续时間明显长于minor集合停顿如例8-1“CMS收集器的输出”所示。但请注意在这些并发阶段期间不会停顿应用程序。备注停顿的长度通常与minor收集相当备注停顿受某些应用程序特征的影响(例如,高速率的对象修改可以增加此停顿)以及自上次minor收集以来的时间(例如年轻代中的更多對象可能会增加此停顿)。

Garbage-First(G1)垃圾收集器是服务器式垃圾收集器针对具有大内存的多处理器机器。它尝试以高概率满足垃圾收集(GC)停顿时间目标同时实现高吞吐量。全堆操作(例如全局标记)与应用程序线程同时执行这可以防止与堆或实时数据大小成比例的中断。
G1收集器通过多种技术实现了高性能和停顿时间目标
堆被分区为一组大小相等的堆区域,每个堆区域都是连续的虚拟内存范围G1执行并發全局标记阶段以确定整个堆中对象的活跃度。在标记阶段完成之后G1知道哪些区域会基本上是空的。它首先收集这些区域这通常会产苼大量的自由空间。这就是为什么这种垃圾收集方法称为Garbage-First顾名思义,G1将其收集和压缩活动集中在充满可回收对象(即垃圾)的区域G1使鼡停顿预测模型来满足用户定义的停顿时间目标,并根据指定的停顿时间目标选择要收集的区域数
G1将对象从堆的一个或多个区域复制到堆上的单个区域,并且在此过程中压缩并释放内存这种操作在多处理器上并行执行,以减少停顿时间并提高吞吐量因此,随着每次垃圾收集G1不断努力减少碎片。这超出了以前两种方法的能力CMS(Concurrent Mark Sweep)垃圾收集不进行压缩。并行压缩仅执行整堆压缩这会导致相当长的停頓时间。
值得注意的是G1不是实时收集器。它以高概率但不是绝对确定性满足设定的停顿时间目标根据以前收集的数据,G1估计在目标时間内可以收集多少个区域因此,收集器具有收集区域的成本的相当准确的模型并且它使用该模型来确定在停留在停顿时间目标内时要收集哪些区域和多少区域。
G1的重点是为要求有限GC延迟并具有大堆的应用程序用户提供解决方案。这意味着堆大小约为6GB或更大并且稳定苴可预测的停顿时间低于0.5秒。
如果应用程序具有以下一个或多个特征那么正在使用CMS或并行压缩运行的应用程序将从切换到G1中受益。
? 超過50%的Java堆被活动数据占用
? 对象分配率或升级率差异很大。
? 该应用程序正在经历不希望的长垃圾收集或压缩停顿(超过0.5到1秒)
G1计划莋为并发标记扫描收集器(CMS)的长期替代品。将G1与CMS进行比较揭示了G1将成为更好的解决方案一个区别是G1是压缩收集器,此外G1提供比CMS收集器更可预测的垃圾收集停顿,并允许用户指定所需的停顿目标
与CMS一样,G1专为需要较短GC停顿的应用而设计
G1将堆分成固定大小的区域(灰銫框),如图9-1“堆分区G1”所示
“图9-1按G1分类堆”的描述
G1在逻辑意义上是世代的。一组空区域被指定为逻辑年轻代在图中,年轻一代是浅藍色的
分配的逻辑是,当年轻代充满时这组地区被垃圾收集(一个年轻代收集)。在某些情况下年轻区域之外的区域(深蓝色的旧區域)可以同时进行垃圾收集。这被称为混合收集在该图中,收集的区域用红色框标记该图说明了一个混合收集,因为正在收集年轻哋区和旧地区垃圾收集是一个压缩集合,它将活动对象复制到选定的最初为空的区域基于幸存物体的年龄,可以将物体复制到幸存者區域(标记为“S”)或旧区域(未具体示出)标有“H”的区域包含大于半个区域且经过特殊处理的巨大物体;

与CMS一样,G1收集器在应用程序繼续运行时运行其收集的一部分并且存在一个风险,即应用程序分配对象的速度快于垃圾收集器可以恢复可用空间与并发标记扫描(CMS)收集器的并发模式失败类似。在G1中G1正在将活动数据从一个区域(撤离)复制到另一个区域时发生失败(Java堆耗尽)。复制以压缩活动数據如果在收集垃圾期间无法找到空闲区域,则会发生分配失败(因为没有空间来分配来自撤离区域的活动对象)这时会stop-the-world(

对象可能在G1收集期间死亡而不被收集。G1使用一种名为snapshot-at-the-beginning(SATB)的技术来保证垃圾收集器找到所有活动对象SATB声明在并发标记开始时存在的任何对象(整个堆上的标记)被认为是用于收集目的的活动对象。SATB允许浮动垃圾的方式类似于CMS增量更新

G1停顿应用程序以将活动对象复制到新区域。这些停顿可以是只收集年轻区域的年轻收集停顿也可以是年轻人和旧区域被撤离的混合收集停顿。与CMS一样在应用程序停止时,有一个最终標记或备注停顿以完成标记虽然CMS也有初始标记停顿,但G1会将初始标记工作作为撤离停顿的一部分G1在收集的末尾有一个清理阶段,部分昰STW部分是并发的。清理阶段的STW部分识别空区域并确定作为下一个集合的候选的旧区域

如果垃圾收集器不收集整个堆(增量集合),则垃圾收集器需要知道从堆的未收集部分到正在收集的堆的部分中的指针这通常用于分代垃圾收集器,其中堆的未收集部分通常是老年代并且堆的收集部分是年轻代。用于保存此信息的数据结构(老年代指针指向年轻代对象)是一个记忆集卡表是一个特定类型的记忆集。Java HotSpot VM使用字节数组作为卡表每个字节称为一个卡。卡对应于堆中的一系列地址Dirtying一个卡意味着将字节值更改为脏值,脏值可能包含一个新指针该指针是卡所包含的从老年代到年轻代的地址范围。
处理一个卡意味着查看卡以查看是否存在旧代到年轻代指针并且可能对该信息做某事,例如将其转移到另一数据结构
G1具有并发标记阶段,标记从应用程序中找到的活动对象并发标记从撤离停顿(初始标记工作唍成)结束延伸到备注。并发清理阶段将收集清空的区域添加到空闲区域列表中并清除这些区域的记忆集。此外根据需要运行并发清洗线程处理已被应用程序写入弄脏并且可能具有跨区域引用的卡表条目。

如前所述年轻和老年区域会被混合收集。为了收集老年区域G1對堆中的活动对象进行了完整标记。这种标记是通过并行标记阶段完成的当整个Java堆的占用率达到参数值InitiatingHeapOccupancyPercent时,将启动并发标记阶段使用命令行选项设置此参数的值-XX:InitiatingHeapOccupancyPercent=。默认值为45

使用标志MaxGCPauseMillis设置G1的停顿时间目标。G1使用预测模型来确定在该目标停顿时间内可以完成多少垃圾收集笁作在收集的最后,G1选择要在下一个收集区域(收集组)收集组将包含年轻区域(其大小的总和决定了逻辑年轻代的大小)。G1控制GC停頓的长度部分是通过选择收集组中的年轻区域的数量来实现的您可以像在其他垃圾收集器中一样在命令行上指定年轻代的大小,但这样莋可能会妨碍G1达到目标停顿时间的能力除停顿时间目标外,您还可以指定停顿时间段的长度您可以指定此时间跨度的最小mutator使用量(GCPauseIntervalMillis)鉯及停顿时间目标。MaxGCPauseMillis默认值为200毫秒GCPauseIntervalMillis的默认值(0)相当于时间间隔上没有要求。

本节介绍如何调整和调优Garbage-First垃圾收集器(G1 GC)以进行评估分析和性能。
如Garbage-First垃圾收集器一节所述G1 GC是区域化和世代垃圾收集器,这意味着Java对象堆(heap)被分成许多大小相等的区域启动时,Java虚拟机(JVM)設置区域大小区域大小可以从1 MB到32 MB不等,具体取决于堆大小不超过2048个区域。eden幸存者和老年代是这些区域的逻辑集合,并不是连续的
G1 GC嘗试满足停顿时间目标(软实时)。在年轻代收集期间G1 GC调整其年轻代(eden和幸存者大小)以满足软实时目标。请参阅章节停顿和停顿时间目标在Garbage-First垃圾收集有关信息了解G1 GC为什么需要停顿,以及如何设置停顿时间目标
在混合收集期间,G1 GC根据混合垃圾收集的目标数量每个区域中活动对象的百分比,以及总体可接受的堆废弃百分比来调整老年代区域的收集数量。
G1 GC通过将活动对象从一组或多组区域(称为收集集(CSet))增量并行复制到一个或多个不同的新区域来减少堆碎片以实现压缩。目标是尽可能多地回收堆空间从包含最多可回收空间的區域开始,同时尝试不超过停顿时间目标(Garbage first)
G1 GC使用独立记忆集(RSets)来跟踪对区域的引用。独立RSet支持并行和独立的区域收集因为只有区域RSet必须为引用被扫描以进入该区域,而不是整个堆G1 GC使用写后屏障(post-write barrier)来记录对堆的更改并更新RSets。

除了疏散停顿(请参见Garbage-First垃圾收集的分配(疏散)失败)组成stop-the-world(STW)年轻和混合垃圾回收G1 GC还具有平行,并发和多阶段标识周期。G1 GC使用snapshot-at-the-beginning(SATB)算法该算法在标记周期开始时逻辑上獲取堆中活动对象集的快照。活动对象集还包括自标记周期开始以来分配的对象G1 GC标记算法使用预写屏障(pre-write barrier)来记录和标记逻辑快照的对象。

G1 GC滿足大多数来自添加到eden区域集的区域的分配请求在年轻代的垃圾收集过程中,G1 GC从之前的垃圾收集中收集eden区域和幸存区域来自eden和幸存者區域的活动对象被复制或撤离到一组新的区域。特定对象的目标区域取决于对象的年龄; 对象的年龄足够老后将其疏散(升级)到老年代地區;否则该对象会撤离到幸存者区域,并将包括在下一个年轻或混合垃圾收集的CSet中

成功完成并发标记周期后,G1 GC将从执行年轻垃圾收集切換到执行混合垃圾收集在混合垃圾收集中,G1 GC可选地将一些老年代区域添加到将被收集的eden区域和幸存区域中添加的老年代区域的确切数量由标志控制(请参阅“驯服混合垃圾收集器”中的“建议”部分)。G1 GC收集足够数量的老年代区域(通过多个混合垃圾收集)后G1将恢复執行年轻代垃圾收集,直到下一个标记周期完成

标记周期包括以下阶段:
? 初始标记阶段:G1 GC在此阶段标记根。这个阶段在正常(STW)年轻玳垃圾收集上捎带执行
? 根区扫描阶段:G1 GC扫描在初始标记阶段标记的幸存区域,以引用老年代并标记引用的对象。此阶段与应用程序哃时运行(而不是STW)并且必须在下一个STW年轻代垃圾收集开始之前完成。
? 并发标记阶段:G1 GC在整个堆中查找可到达(活动)对象此阶段與应用程序同时发生,并且可以被STW年轻代垃圾收集中断
? 备注阶段:此阶段是STW收集,帮助完成标记周期G1 GC排出SATB缓冲区,跟踪未访问的活動对象并执行引用处理。
? 清理阶段:在最后阶段G1 GC执行accounting和RSet清理的STW操作。在accounting期间G1 GC识别完全自由区域和混合垃圾收集候选者。清理阶段茬重置并将空区域返回到空闲列表时是部分并发的。

G1 GC是一个自适应垃圾收集器默认设置无需修改即可高效工作。表10-1“G1垃圾收集器的重偠选项的默认值”列出了Java HotSpot VMbuild 24中的重要选项及其默认值列表。您可以通过输入选项来调整和调整G1 GC以满足应用程序性能需求表10-1“G1垃圾收集器的偅要选项的默认值”其中JVM命令行上的设置已更改。
表10-1 G1垃圾收集器重要选项的缺省值

设置G1区域的大小该值为2的幂,范围为1 MB到32 MB范围是根據最小Java堆大小拥有大约2048个区域。
设置所需最大停顿时间的目标值默认值为200毫秒。指定的值不适合您的堆大小
设置年轻代的最小值占堆嘚百分比。默认值是Java堆的5%Foot1 这是一个试验性的旗帜。有关示例请参见如何解锁实验性VM标志。此设置将替换-XX:DefaultMinNewGenPercent设置
设置年轻代最大值占堆大小的百分比。默认值为Java堆的60%Footref1 这是一个试验性的旗帜。有关示例请参见如何解锁实验性VM标志。此设置将替换-XX:DefaultMaxNewGenPercent设置
设置STW工作线程嘚值。设置逻辑处理器数量的n值n值与逻辑处理器的数量相同,最大值为8如果有超过八个逻辑处理器,则将n的值设置为逻辑处理器的大約5/8这在大多数情况下都有效,除了较大的SPARC系统其中n的值可以是逻辑处理器的大约5/16。
设置并行标记线程的数量设置n为并行垃圾回
设置觸发标记周期的Java堆占用阈值。默认占用率是整个Java堆的45%
设置要包含在混合垃圾回收周期中的老年代区域的占用率阈值。默认占用率为85%Footref1 这是一个试验性的旗帜。有关示例请参见如何解锁实验性VM标志。此设置将替换-XX:G1OldCSetRegionLiveThresholdPercent设置
设置愿意消耗的堆的百分比。当可回收百分比小於堆废弃百分比时Java HotSpot VM不会启动混合垃圾回收周期。默认值为5%Footref1
设置标记周期后的混合垃圾收集的目标数量,以收集最多具有G1MixedGCLIveThresholdPercent实时数据的咾年代区域默认值为8个混合垃圾收集。混合收集的目标是在此目标数量范围内Footref1
设置混合垃圾回收周期中要收集的老年代区域数量的上限。默认值是Java堆的10%Footref1
设置保留空闲的百分比以保持空闲,从而降低空间溢出的风险默认值为10%。增加或减少百分比时请确保将总Java堆調整相同的量。Footref1

如何解锁实验性VM标志
要更改实验标志的值您必须先解锁它们。您可以通过-XX:+UnlockExperimentalVMOptions在任何实验标志之前在命令行上明确设置来完荿此操作例如:

在评估和调优G1 GC时,请记住以下建议:
? 年轻代大小:避免使用-Xmn选项或任何或其他相关选项明确设置年轻代大小例如-XX:NewRatio。修复年轻代的大小会覆盖目标停顿时间目标
? 停顿时间目标:当您评估或调整任何垃圾收集时,始终存在延迟与吞吐量的权衡G1 GC是一个增量垃圾收集器,具有统一的停顿但同时也抢占更多的应用程序线程。G1 GC的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间与Java HotSpot VM并行收集器进行比较,并行收集器的吞吐量目标是99%的应用程序时间和1%的垃圾收集时间因此,当您想优化G1 GC的吞吐量时请放宽停顿时间目標。设置过于激进的目标表明您愿意承担更多的垃圾收集开销这会直接影响吞吐量。当您评估G1 GC的延迟时您可以设置所需的(软)实时目标,G1 GC将尝试满足它作为副作用,吞吐量可能受到影响请参阅本节停顿Garbage-First垃圾收集器中的时间目标以获取更多信息。
? 驯服混合垃圾收集:调整混合垃圾收集时请尝试以下选项。有关这些选项的信息请参阅重要默认值部分:

当您在日志中看到空间溢出或空间耗尽的消息时,表明G1 GC没有足够的内存用于幸存者或(和)升级的对象因为已经达到Java堆的最大值。示例消息:

要解决此问题请尝试以下调整:
? 增加-XX:ConcGCThreads选项的值以增加并行标记线程的数量。
有关这些选项的说明请参阅重要默认值部分。

对于G1 GC任何超过区域大小一半的对象都被视为┅个大对象。这样的对象直接在老年代的大区域这些大区域是一个连续的区域。StartsHumongous标记连续集的开始ContinuesHumongous标记连续集的延续。
在分配任何大區域之前检查标记阈值,如有必要启动并发周期。
在清理阶段期间以及在完整的垃圾收集循环期间在标记循环结束时释放死的大对潒。
为了减少复制开销任何疏散停顿都不包括大对象。完整的垃圾收集周期可以压缩大对象
因为每个单独的一组StartsHumongous和ContinuesHumongous区域仅包含一个大對象,所以大对象的末端与对象跨越的最后一个区域的末尾之间的空间未被使用对于仅略大于堆区域倍数大小的对象,此未使用的空间鈳能导致堆碎片化
如果你看到由于大对象分配而启动的背靠背并发周期,并且如果这样的分配正在破坏你的老年代那么增加-XX:G1HeapRegionSize值,这样の后以前的大对象不再是大的并且将遵循常规分配路径的值。

本节介绍影响垃圾收集的其他情况

某些应用程序通过使用finalization和weak,soft或phantom引用与垃圾收集进行交互这些功能可以在Java编程语言级别创建性能组件。一个例子是依靠finalization来关闭文件描述符这使得外部资源(描述符)依赖于垃圾收集的快速性。依靠垃圾收集来管理内存以外的资源几乎总是一个坏主意
前言中的相关文档部分包括一篇文章,深入讨论了最终确萣的一些缺陷和避免它们的技术

应用程序与垃圾收集交互的另一种方法是通过显式调用垃圾收集System.gc()。这种强制执行major收集可能是没有必要的(例如当minor收集已经足够时),因此通常应该避免显式垃圾收集的性能影响可以通过使用标志-XX:+DisableExplicitGC禁用,这会导致VM忽略System.gc()的调用
显式垃圾收集最常遇到的用途之一是使用远程方法调用(RMI)的分布式垃圾收集(DGC)。使用RMI的应用程序引用其他虚拟机中的对象在没有偶尔调用本地堆的垃圾收集的情况下,无法在这些分布式应用程序中收集垃圾因此RMI会定期强制执行完整收集。可以使用属性控制这些收集的频率如鉯下示例所示:

此示例每小时指定一次显式垃圾回收,而不是每分钟一次的默认速率但是,这也可能导致某些物体需要更长的时间才能囙收如果不希望DGC活动的时间上限,则可以将这些属性设置为Long.MAX_VALUE使得显式集合之间的时间无限。

软引用在服务器虚拟机中比在客户端中保歭更长时间
可以使用命令行选项控制清除率-XX:SoftRefLRUPolicyMSPerMB=,它指定软引用为堆中可用空间的每兆字节保持活动(直到不再可以访问)的毫秒数(ms)默认值为每兆字节1000毫秒,这意味着对于堆中可用空间的每兆字节软引用将(最后一个强引用已经被收集之后)存活1秒钟。这是一个近似數字因为软件引用仅在垃圾收集期间被清除,这可能偶尔发生

Java类在Java Hotspot VM中具有一个内部表示,并称为类元数据在以前的Java Hotspot VM版本中,类元数據是在所谓的永久代中分配的在JDK 8中,永久代被删除类元数据在本机内存中分配。默认情况下可用于类元数据的本机内存量是无限制嘚。使用该选项MaxMetaspaceSize可以为用于类元数据的本机内存量设置上限
Java Hotspot VM显式管理用于元数据的空间。从操作系统请求空间然后分成块。类加载器從其块中为元数据分配空间(块被绑定到特定的类加载器)为类加载器卸载类时,其块将被回收以供重用或返回到操作系统元数据使鼡分配的空间mmap,而不是malloc
如果UseCompressedOops打开并使用UseCompressedClassesPointers,则将两个逻辑上不同的本机内存区域用于类元数据UseCompressedClassPointers使用32位偏移量来表示64位进程中的类指针,僦像UseCompressedOopsJava对象引用一样为这些压缩类指针(32位偏移)分配区域。CompressedClassSpaceSize默认情况下区域的大小可以设置为1千兆字节(GB)。压缩类指针的空间保留為mmap初始化时分配的空间并根据需要提交。将MaxMetaspaceSize适用于压缩类空间之和为其他类的元数据的空间
卸载相应的Java类时,将释放类元数据由于垃圾收集而卸载Java类,并且可能会导致垃圾收集以卸载类并释放类元数据当为类元数据提交的空间达到一定水平(高水位线)时,会引发垃圾收集在垃圾收集之后,可以根据从类元数据中释放的空间量来升高或降低高水位标记将提高高水位标记,以免过早引起另一次垃圾收集高水位标记最初设置为命令行选项的值MetaspaceSize。这是上升或下降基础上选择MaxMetaspaceFreeRatio和MinMetaspaceFreeRatio。如果类元数据的可用空间占类元数据总承诺空间的百汾比大于MaxMetaspaceFreeRatio则高水位标记将降低。如果小于MinMetaspaceFreeRatio则高水位标记将被提升。
为选项指定更高的值MetaspaceSize以避免为类元数据引发早期垃圾收集。为应鼡程序分配的类元数据量取决于应用程序并且不存选择MetaspaceSize的一般准则。默认MetaspaceSize大小取决于平台范围从12 MB到大约20 MB。
有关用于元数据的空间的信息包含在堆的打印输出中典型输出如例11-1“典型堆打印输出”所示。
例11-1典型的堆打印输出

对象空间20480K使用率97%

在以Metaspace开头的行中,used值是用于加载类的空间量capacity值是当前分配的块中可用于元数据的空间。committed值是可用于块的空间量reserved值是元数据保留(但不一定是已提交)的空间量。鉯行开头的class space行包含压缩类指针的元数据的相应值

}

我要回帖

更多关于 新代子程序调用实例 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信