关于gc:当小白遇到FullGC-京东云技术团队

起初没有人在意这场GC,直到它影响到了每一天!前言本文记录了一次排查FullGC导致的TP99过高过程,介绍了一些排查时思路,线索以及工具的应用,心愿可能帮忙一些老手在排查问题没有很好的思路时,提供一些思路,让小白也能轻松解决FullGC问题,文中理论提到的参数配置不肯定适宜其余业务场景,在调优本人的我的项目时还是须要理论试验过能力得出最佳参数配置 我也是小白,如有不合理的中央,欢送大佬们进行斧正 因为线上服务器,咱们大部分是没有SSH权限的,没有方法间接执行命令获取容器信息,所以排查过程中只能借助平台提供的工具,平台提供的工具还是挺全的,本文次要用到的工具有: JDOS容器智能监控,JDOS过程查问,SGM容器监控信息,SGM办法调用查问 以下几个工具简略介绍: http://sgm-server.jd.com/http://jagile.jd.com/jdosCD/jdt/appsJDOS容器智能监控: 查看容器的CPU,内存,磁盘,IO等信息JDOS过程查问: 查看Java过程编号,执行罕用的Java内存过程查看命令SGM容器监控信息: 查看JVM虚拟机内存变更历史记录SGM办法调用查问: 查看某一次要害接口调用的高低依赖,工夫散布起因 - 偶然呈现接口超时一开始偶然会收到报警邮件,显示有些接口调用工夫比拟长,抽查了一些接口,发现大部分都是调用上游JSF工夫比拟长,导致响应比较慢,这时候就没太在意,接下来持续察看了几天,发现一个法则,大部分邮件都是每天10点 排查定位问题首先确认了10点这个工夫点有没有定时工作之类的操作,通过询问确定这个工夫点是仓库出库高峰期,导致业务量呈现峰值(调用质变大可能是激发FullGC问题,成为问题暴漏的导火线)第二部就是确认是数据库起因,还是业务代码,还是JSF上游接口达到极限起因,到这一步还是未知的,在这用到了SGM的接口调用查问工具,下图中咱们看到,这次调用JSF也是挺高的(这个没有太好方法,除非让上游优化,所以临时疏忽),然而还有一个是logic,这个就是逻辑解决,如果没有那个FullGC提醒,就须要去剖析代码的解决是否有问题,这通过那行红色字体的提醒,很显然咱们确定了是FullGC导致的问题咱们去查看一下容器的FullGC状况,的确发现这个工夫点的FullGC特地频繁,到此曾经把问题范畴定位到就是FullGC导致的 FullGC问题排查Full GC 触发条件:到这里咱们须要确定一个问题 : “触发FullGC的条件是什么?”,老手能够去博客搜寻,当然最好是能记住这个知识点。留神这不是确定“什么起因导致的FullGC?”,因为这个问题起因太多了,咱们要一步一步排查。 上面是我查到的材料,粘到这里供参考. Minor GC触发条件:当Eden区满时,触发Minor GC。Full GC触发条件:(1)调用System.gc()时,零碎倡议执行Full GC,然而不必然执(2)老年代空间有余(3)办法区空间有余(4)通过Minor GC后进入老年代的均匀大小大于老年代的可用内存(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代这里在代码中并没有找到System.gc()的显示调用,个别咱们也不会调用这个办法,所以咱们间接看第二种状况,到SGM中查看老年代变动,后果发现老年代频繁达到90%,而这个工夫正好能够跟下面GC工夫对上. 对象进入老年代的几种状况咱们都晓得,老年代的对象应该是存活工夫很长的对象,然而咱们发现这些对象都在FullGC时被开释掉了,他们为什么到了老年代呢? 这时候咱们须要确定的第二个问题是:“什么状况下对象会进入老年代?” 查资料后有以下几种状况 年龄够了: 躲过15次(默认配置是15次) minorGC 之后从新生代进入老年代;大对象: 大对象间接进入老年代。有一个 JVM 参数 '-XX:PretenureSizeThreshold' 设置值为字节数,创立超过该大小的对象间接进入老年代,如果没有配置这个参数,这个值如同默认是1M。动静年龄判断:以后放对象的 Survivor 区,雷同年龄的一批对象(以及小于该年龄)的总内存大于该区的内存的50%,大于该年龄的其余老对象,就会进入老年代(例如1,2,3岁年龄的对象占了 S 区的50%以上,就会把大于3岁的对象挪动到老年代去。所以尽量让 S 区中的对象,占比尽量少于 50%);剩的总量太多: Eden 区存活对象太多,超过了 Survivor 的大小,就间接把这些对象都转移到老年代去。(JDK1.8 空间担保机制)首先剖析第一种状况,如果呈现大批量这样的对象,代码中呈现了长时间援用(例如:动态Map只加不删),然而咱们能够看到,这些对象在每次FullGC都被开释掉了,阐明这批对象存活的工夫并不长, 而且代码排查也没发现这种代码,临时排除这种状况(这的代码因为是工具包的代码,所以没有太深纠,这为续集留个伏笔). 第二种状况,大对象,咱们到JDOS下载下来JMap-dump内存快照和JMap-Histo对象统计信息,通过对FullGC钱dump剖析,联合GC前GC后对象统计后果,并没有发现大量的大对象,这个根本也排除 通过JMAT(Eclipse Memory Analysis Tools)导入dump文件进行剖析,内存透露问题个别咱们间接选Leak Suspects即可,mat给出了内存透露的倡议。另外也能够抉择Top Consumers来查看最大对象报告。和线程相干的问题能够抉择thread overview进行剖析。除此之外就是抉择Histogram类概览来本人缓缓剖析,大家能够搜搜mat的相干教程。 接下来就是第三种和第四种状况,这时候咱们须要取查看年老代三块区域的变动,尤其是Survivor区域,下图是过后一个状况,S区大小始终在变动,而且基本一致放弃在50%以上,这时候想到了一个JVM高版本个性,会主动关上UseAdaptiveSizePolicy(动静调整),查资料后发现,好多人反馈这个参数会导致对象跨过S区,间接跑到老年代的状况,咱们看到在调用量继续很高的状况,尽然调整到了17M,这必定会导致包容不下过后存活的对象 UseAdaptiveSizePolicy开关参数-XX:+UseAdaptiveSizePolicy是一个开关参数,当这个参数关上之后,虚构机会依据以后零碎的运行状况收集性能监控信息,动静调整这些参数以提供最合适的进展工夫或最大的吞吐量,这种调节形式称为GC自适应的调节策略(GC Ergonomics)。 ...

August 23, 2023 · 1 min · jiezi

关于gc:亚毫秒-GC-暂停到底有多香JDK17ZGC-初体验|得物技术

1 前言垃圾回收器的暂停问题始终是Java工程师关注的重点,特地是对实时响应要求较高的服务来说,CMS和G1等支流垃圾回收器的数十毫秒乃至上百毫秒的暂停工夫相当致命。此外,调优门槛也绝对较高,须要对垃圾回收器的外部机制有肯定的理解,才可能进行无效的调优。为了解决此类问题,JDK 11开始推出了一种低提早垃圾回收器ZGC。ZGC应用了一些新技术和优化算法,能够将GC暂停工夫管制在10毫秒以内,而在JDK 17的加持下,ZGC的暂停工夫甚至能够管制在亚毫秒级别! 2 ZGCZGC相干介绍、原理,网上曾经有很多相似文章,这里只做简略介绍。 2.1 设计指标ZGC 最后在 JDK 11 中作为试验性功能引入,并在 JDK 15 中发表为生产就绪。作为一款低提早垃圾收集器,旨在满足以下指标: 8MB到16TB的堆大小反对10ms最大GC临时最蹩脚的状况下吞吐量会升高15%(低延时换吞吐量很值,吞吐量扩容即可解决) 2.2 ZGC 内存散布ZGC与传统的CMS、G1不同、它没有分代的概念,只有相似G1的Region概率,ZGC 的 Region能够具备如下图所示的大中下三类容量: 小型 Region(Small Region):容量固定为2MB,用于搁置小于 256KB的小对象。中型 Region(Medium Region):容量固定为 32MB,用于搁置大于 256KB然而小于 4MB的对象。大型 Region(Large Region):容量不固定,能够动态变化,但必须为 2MB的整数倍,用于搁置 4MB或以上的大对象。每个大型 Region中会寄存一个大对象,这也预示着尽管名字叫“大型 Region”,但它的理论容量齐全有可能小于中型Region,最小容量可低至4MB。大型 Region在ZGC的实现中是不会被重调配的(重调配是ZGC的一种解决动作,用于复制对象的收集器阶段)因为复制大对象的代价十分高。 2.3 GC工作过程与CMS中的ParNew和G1相似,ZGC也采纳标记-复制算法,不过ZGC通过着色指针和读屏障技术,解决了转移过程中精确拜访对象的问题,在标记、转移和重定位阶段简直都是并发执行的,这是ZGC实现进展工夫小于10ms指标的最要害起因。 从上图中能够看出,ZGC只有三个STW阶段:初始标记,再标记,初始转移。具体转移过程,网上有大量相似文章,这里不做具体介绍,大家有趣味能够参考以下文章: 新一代垃圾回收器ZGC的摸索与实际ZGC 最新一代垃圾回收器 | 程序员进阶 3 为什么抉择JDK17呢?JDK 17于9月14日公布,是一个长期反对(LTS)版本,这意味着它将在很多年内失去反对和更新。这也是第一个LTS版本,其中蕴含了一个可用于生产环境的ZGC版本。回顾一下,ZGC的试验版本曾经蕴含在JDK 11(之前的LTS版本)中,而第一个可用于生产环境的ZGC版本呈现在JDK 15(一个非LTS版本)中。 4 降级过程从JDK8+G1降级到JDK17+ZGC,次要是在代码层面和JVM启动参数层面的做适配。 4.1 JDK下载首先jdk17抉择的是openjdk,下载地址:https://jdk.java.net/archive/,抉择版本17 GA 4.2 代码适配JDK11移除了 Java EE and CORBA 的模块我的项目中如果用到javax.annotation.、javax.xml.等等结尾的包,须要手动引入对应依赖 <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId></dependency><dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId></dependency><dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId></dependency><dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId></dependency>maven相干依赖版本升级<!-- 仅供参考 --><maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version><maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version><maven-resources-plugin.version>3.2.0</maven-resources-plugin.version><maven-jar-plugin.version>3.2.0</maven-jar-plugin.version><maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version><maven-deploy-plugin.version>3.0.0-M1</maven-deploy-plugin.version><maven-release-plugin.version>3.0.0-M1</maven-release-plugin.version><maven-site-plugin.version>3.9.1</maven-site-plugin.version><maven-enforcer-plugin.version>3.0.0-M2</maven-enforcer-plugin.version><maven-project-info-reports-plugin.version>3.1.0</maven-project-info-reports-plugin.version><maven-plugin-plugin.version>3.6.1</maven-plugin-plugin.version><maven-javadoc-plugin.version>3.3.0</maven-javadoc-plugin.version><maven-source-plugin.version>3.2.1</maven-source-plugin.version><maven-jxr-plugin.version>3.0.0</maven-jxr-plugin.version>Lombok版本升级https://projectlombok.org/changelog<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <!-- <version>1.16.20</version>--> <version>1.18.22</version></dependency> ...

June 16, 2023 · 1 min · jiezi

关于gc:线上FullGC问题排查实践手把手教你排查线上问题-京东云技术团队

作者:京东科技 韩国凯 一、问题发现与排查1.1 找到问题起因问题起因是咱们收到了jdos的容器CPU告警,CPU使用率曾经达到104% 察看该机器日志发现,此时有很多线程在执行跑批工作。失常来说,跑批工作是低CPU高内存型,所以此时思考是FullGC引起的大量CPU占用(之前有相似状况,告知用户后重启利用后解决问题)。 通过泰山查看该机器内存应用状况: 能够看到CPU的确使用率偏高,然而内存使用率并不高,只有62%,属于失常范畴内。 到这里其实就有点蛊惑了,按情理来说此时内存应该曾经打满才对。 前面依据其余指标,例如流量的忽然进入也狐疑过是jsf接口被忽然大量调用导致的cpu占满,所以内存使用率不高,不过前面都缓缓排除了。其实在这里就有点束手无策了,景象与猜想不符,只有CPU增长而没有内存增长,那么什么起因会导致单方面CPU增长?而后又朝这个方向排查了半天也都被否定了。 前面忽然意识到,会不会是监控有“问题”? 换句话说应该是咱们看到的监控有问题,这里的监控是机器的监控,而不是JVM的监控! JVM的应用的CPU是在机器上能体现进去的,而JVM的堆内存高额应用之后在机器上体现的并不是很显著。 遂去sgm查看对应节点的jvm相干状况: 能够看到咱们的堆内存老年代的确有过被打满而后又清理后的状况,查看此时的CPU应用状况也能够与GC工夫对应上。 那么此时能够确定,是Full GC引起的问题。 1.2 找到FULL GC的起因咱们首先dump出了gc前后的堆内存快照, 而后应用JPofiler进行内存剖析。(JProfiler是一款堆内存剖析工具,能够间接连接线上jvm实时查看相干信息,也能够剖析dump进去的堆内存快照,对某一时刻的堆内存状况进行剖析) 首先将咱们dump进去的文件解压,批改后缀名.bin,而后关上即可。(咱们应用行云上自带的dump小工具,也能够本人去机器上通过命令手工dump文件) 首先抉择Biggest Objects,查看过后堆内存中最大的几个对象。 从图中能够看出,四个List对象就占据了近900MB的内存,而咱们刚刚看到堆内存最大也只有1.3GB,因而再加上其余的对象,很容易就会把老年代占满引发full gc的问题。 抉择其中一个最大的对象作为咱们要查看的对象 这个时候咱们曾经能够定位到对应的大内存对象对应的地位: 其实至此咱们曾经可能大略定位出问题所在,如果还是不确定的话,能够查看具体的对象信息,办法如下: 能够看到咱们的大List对象,其实外部是很多个Map对象,而每个Map对象中又有很多键值对。 在这里也能够看到Map中的相干属性信息。 也能够在以下界面间接看到相干信息: 而后一路点上来就能够看到对应的属性。 至此,咱们实践上曾经找到了大对象在代码中的地位。 二、问题解决2.1 找到大对象在代码中的地位与问题的根本原因首先咱们根据上述过程找到对应地位与逻辑 咱们的我的项目中大略逻辑是这样的: 首先会解析用户上传的Excel样本,并将其加载到内存中作为一个List变量,即咱们上述看到的变量。一个20w的样本,此时字段数量有a个,大略占用空间100mb左右。而后遍历循环用户样本,依据用户样本中的数据,再减少一些额定的申请数据,依据此数据申请相干后果。此时字段数量有a+n个,占用空间曾经在200mb左右。循环实现后将此200mb的数据存入缓存。开始生成excel,将200mb数据从缓存中取出,并依据之前记录的a个字段,取出初始的样本字段填充至excel。用流程图示意为: 联合一些具体排查问题的图片: 其中一个景象是每次gc后的最小内存正在逐渐变大,对应上述步骤中第二步,内存正在逐渐收缩。 论断: 将用户上传的excel样本加载到内存中,并将其作为一个List<Map<String, String>>的构造存储起来,首先一个20mb的excel文件以此形式存储会收缩占用120mb左右堆内存,此步骤会大量占用堆内存,并且因为工作逻辑起因,该大对象内存会在jvm中存在长达4-12小时之久,导致一但工作过多,jvm堆内存很容易被打满。 这里列举了为什么应用HashMap会导致内存收缩,其次要起因是存储空间效率比拟低: 一个Long对象占内存计算:在HashMap<Long,Long>构造中,只有Key和Value所寄存的两个长整型数据是无效数据,共16字节(2×8字节)。这两个长整型数据包装成java.lang.Long对象之后,就别离具备8字节的MarkWord、8字节的Klass指针,再加8字节存储数据的long值(一个包装对象占24字节)。 而后这2个Long对象组成Map.Entry之后,又多了16字节的对象头(8字节MarkWord+8字节Klass指针=16字节),而后一个8字节的next字段和4字节的int型的hash字段(8字节next指针+4字节hash字段+4字节填充=16字节),为了对齐,还必须增加4字节的空白填充,最初还有HashMap中对这个Entry的8字节的援用,这样减少两个长整型数字,理论消耗的内存为(Long(24byte)×2)+Entry(32byte)+HashMapRef(8byte)=88byte,空间效率为无效数据除以全副内存空间,即16字节/88字节=18%。 ——《深刻了解Java虚拟机》5.2.6 以下是刚上传的excel中dump出的堆内存对象,其占用的内存达到了128mb,而上传的excel理论只有17.11mb。 空间效率17.1mb/128mb≈13.4% 2.2 如何解决此问题暂且不探讨上述流程是否正当,解决办法个别能够分为两类,一类是治标,即不把该对象放入jvm内存中,转而存入缓存中,不在内存中则大对象问题天然迎刃而解。另一类是治本,即放大该大内存对象,在日常应用场景下使其个别不会触发频繁的full gc问题。 两种形式各有优劣: 2.2.1 激进医治:不把他存入内存解决逻辑也很简略,例如在加载数据时,将其依照样本加载数据一条一条存入redis缓存,而后咱们只须要晓得样本中有多少的数量,依照数量的先后顺序从缓存中取出数据,即可解决该问题。 长处:能够从根本上解决此问题,当前基本上不会存在该问题,数据量再大只须要增加相应的redis资源即可。 毛病:首先会减少许多redis缓存空间耗费,其次从显示思考对于咱们我的项目来说,此处代码古老且艰涩难懂,改变须要较大工作量与回归测试。 ...

May 5, 2023 · 1 min · jiezi

关于gc:记一次线上FGC问题排查

引言本文记录一次线上 GC 问题的排查过程与思路,心愿对各位读者有所帮忙。过程中也走了一些弯路,当初有工夫积淀下来思考并总结进去分享给大家,心愿对大家今后排查线上 GC 问题有帮忙。 背景服务新性能发版一周后下午,忽然收到 CMS GC 告警,导致单台节点被拉出,随后集群内每个节点先后都产生了一次 CMS GC,拉出后的节点垃圾回收后接入流量恢复正常(预先排查发现被重启了)。 告警信息如下(已脱敏): 多个节点简直同时产生 GC 问题,且排查天然流量监控后发现并未有显著增高,根本能够确定是有 GC 问题的,须要解决。 排查过程GC 日志排查GC 问题首先排查的应该是 GC 日志,日志能可能清晰的断定产生 GC 的那一刻是什么导致的 GC,通过剖析 GC 日志,可能清晰的得出 GC 哪一部分在出问题,如下是 GC 日志示例: 0.514: [GC (Allocation Failure) [PSYoungGen: 4445K->1386K(28672K)] 168285K->165234K(200704K), 0.0036830 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]0.518: [Full GC (Ergonomics) [PSYoungGen: 1386K->0K(28672K)] [ParOldGen: 163848K->165101K(172032K)] 165234K->165101K(200704K), [Metaspace: 3509K->3509K(1056768K)], 0.0103061 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]0.528: [GC (Allocation Failure) [PSYoungGen: 0K->0K(28672K)] 165101K->165101K(200704K), 0.0019968 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]0.530: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(28672K)] [ParOldGen: 165101K->165082K(172032K)] 165101K->165082K(200704K), [Metaspace: 3509K->3509K(1056768K)], 0.0108352 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]如上 GC 日志能很显著发现导致 Full GC 的问题是:Full GC 之后,新生代内存没有变动,老年代内存应用从 165101K 升高到 165082K (简直没有变动)。这个程序最初内存溢出了,因为没有可用的堆内存创立 70m 的大对象。 ...

January 31, 2023 · 2 min · jiezi

关于gc:GC耗时高原因竟是服务流量小

原创:扣钉日记(微信公众号ID:codelogs),欢送分享,转载请保留出处。简介最近,咱们系统配置了GC耗时的监控,但配置上之后,零碎会偶然呈现GC耗时大于1s的报警,排查花了一些力量,故在这里分享下。 发现问题咱们零碎分多个环境部署,呈现GC长耗时的是俄罗斯环境,其它环境没有这个问题,这里比拟奇怪的是,俄罗斯环境是流量最低的一个环境,而且大多数GC长耗时产生在深夜。 发现报警后,我立马查看了GC日志,如下:  日志中呈现了to-space exhausted,通过一番理解,呈现这个是因为g1在做gc时,都是先复制存活对象,再回收原region,当没有闲暇空间复制存活对象时,就会呈现to-space exhausted,而这种GC场景代价是十分大的。 同时,在这个gc产生之前,还发现了一些如下的日志。 这里能够看到,零碎在调配约30M+的大对象,难道是有代码频繁调配大对象导致的gc问题。 定位大对象产生地位jdk在调配大对象时,会调用G1CollectedHeap::humongous_obj_allocate办法,而应用async-profiler能够很容易晓得哪里调用了这个办法,如下: # 开启收集./profiler.sh start --all-user -e G1CollectedHeap::humongous_obj_allocate -f ./humongous.jfr jps# 进行收集./profiler.sh stop -f ./humongous.jfr jps将humongous.jfr文件用jmc关上,如下: 依据调用栈我发现,这是咱们的一个定时工作,它会定时调用一个接口获取配置信息并缓存它,而这个接口返回的数据量达14M之多。 难道是这个接口导致的gc问题?但这个定时工作调用也不频繁呀,5分钟才调用一次,不至于让gc忙不过来吧! 另一个疑难是,这个定时工作在其它环境也会运行,而且其它环境的业务流量大得多,为什么其它环境没有问题? 至此,我也不确定是这个定时工作导致的问题,还是零碎本身特点导致偶然的高gc耗时。 因为咱们有固定的上线日,于是我打算先优化产生大对象的代码,而后在上线前的期间试着优化一下jvm gc参数。 优化产生大对象的代码咱们应用的是httpclient调用接口,调用接口时,代码会先将接口返回数据读取成String,而后再应用jackson把String转成map对象,简化如下: rsp = httpClient.execute(request);String result = IOUtils.toString(rsp.getEntity().getContent());Map resultMap = JSONUtil.getMapper().readValue(result, Map.class);要优化它也很简略,应用jackson的readValue有一个传入InputStream的重载办法,用它来读取json数据,而不是将其加载成一个大的String对象再读,如下: rsp = httpClient.execute(request);InputStream is = rsp.getEntity().getContent();Map resultMap = JSONUtil.getMapper().readValue(is, Map.class);注:这外面map从逻辑上来说是一个大对象,但对jvm来说,它只是很多个小对象而后用指针连接起来而已,大对象个别由大数组造成,大String之所以是大对象,是因为它外面有一个很大的char[]数组。优化GC参数优化完代码后,我开始钻研优化jvm gc参数了,咱们应用的是jdk8,垃圾收集器是g1,为了了解g1的调优参数,又简略学习了下g1的要害概念。 g1是分region收集的,但region也分年老代与老年代。g1的gc分young gc与mixed gc,young gc用于收集年老代region,mixed gc会收集年老代与老年代region。在mixed gc回收之前,须要先经验一个并发周期(Concurrent Cycles),用来标记各region的对象存活状况,让mixed gc能够判断出须要回收哪些region。并发周期分为如下4个子阶段: a. 初始标记(initial marking) b. 并发标记(concurrent marking) c. 从新标记(remarking) d. 清理(clean up) 须要留神的是,初始标记(initial marking)这一步是借助young gc实现的。在g1中,young gc简直没有什么可调参数,而mixed gc有一些,常见如下: ...

January 7, 2023 · 2 min · jiezi

关于gc:A-Guide-to-the-Go-Garbage-Collector-翻译Go-语言垃圾回收指南

序言本指南文档通过向纯熟的 Go 用户提供对于 Go GC 一些深刻信息,来帮忙他们更好的对本人程序的运行代价的了解。同时也给 Go 用户提供一份如何深刻洞察优化程序资源利用率的指南。本指南并不假如你相熟 GC,然而对 Go 语言的熟知是必须的。 Go 语言负责管理所有的语言的值(values),绝大多数状况下,Go 开发者并不需要关注这些值是如何被存储。当然,这些数据实际上是存储在无限的物理内存中的。 因为内存的限度,因而它必须被审慎的治理,和被回收重用,防止被一个运行的程序耗尽。依据需要调配和回收内存,是 Go 语言负责的工作。 主动回收利用内存被称为垃圾回收(garbage collection)。垃圾回收(简称 GC) 是一个回收被判断为程序不会再应用的内存的零碎。Go 语言提供一个运行时库(runtime library),它随同每个 Go 程序一起执行,提供了 GC 性能。 请留神,在 Go 语言标准中并没有确保存在 GC,标准只提到了底层存储的值是由语言本人治理的。(语言标准中的)这项规范是成心脱漏的,它容许理论的(Go 语言实现)应用齐全不同的内存治理技术。 尽管如此,本指南形容的只是 Go 语言的一个特定实现,可能不适用于其余的实现。再具体一点,本指南只针对 Go 语言官网规范工具链 的实现。在 Gccgo 和 Gollvm 这两个实现中 GC 和(规范工具链)十分类似,有大量雷同的概念,然而实现细节可能不一样。 另外,本文档更新以实用与最新的 Go 版本。目前,本文档形容的是 Go 1.19 的 GC。 Go 的值(Values) 存在于哪里在咱们深刻探讨 GC 之前,先讨论一下那些不须要被 GC 治理的内存。比方,被存储在 local 变量的非指针值很有可能齐全不须要被 Go GC 治理,而是被调配在它具备雷同的词法作用域的内存中。通常这样会比应用 GC 更加高效,因为 Go 编译器可能事后计算出这块内存什么时候被回收,并且收回清理的指令。 咱们称这一类调配为栈调配,因为他的空间正是存在于在执行代码的 goroutine 堆栈上。 ...

January 4, 2023 · 6 min · jiezi

关于gc:JAVAFull-GC-排查

1. 获取pidps -aux | grep java2. 查看堆中对象统计jmap -histo [pid]3. jmapjmap -heap [pid]4. dump剖析jmap -dump:format=b,file=./j.dump [pid]jhat -J-Xmx1024M ./j.dump

October 11, 2021 · 1 min · jiezi

关于gc:垃圾回收机制

残缺高频题库仓库地址:https://github.com/hzfe/aweso... 残缺高频题库浏览地址:https://febook.hzfe.org/ 相干问题什么是内存透露常见的垃圾回收算法如何排查内存透露答复关键点援用计数法 标记革除法 Mark-Compact(标记整顿) Scavenger(清道夫) GC(Garbage Collection,垃圾回收)是一种内存主动管理机制, 垃圾回收器(Garbage Collector)能够主动回收调配给程序的曾经不再应用的内存。常见的 GC 算法有援用计数法和标记革除法等。V8(JavaScript 引擎,提供执行 JavaScript 的运行时环境)的垃圾回收器算法次要由 Mark-Compact 和 Scavenger 形成。 知识点深刻1. 内存透露内存透露是指,该当被回收的对象没有被失常回收,变成常驻老生代的对象,导致内存占用越来越高。内存透露会导致应用程序速度变慢、高延时、解体等问题。 1.1 内存生命周期调配:按需分配内存。应用:读写已调配的内存。开释:开释不再须要的内存。1.2 内存透露常见起因创立全局变量,且没有手动回收。事件监听器 / 定时器 / 闭包等未失常清理。应用 JavaScript 对象来做缓存,且不设置过期策略和对象大小管制。队列拥塞所带来的生产不及时问题。2. Reference Counting(援用计数)Reference Counting 是常见的垃圾回收算法,其外围思路是:将资源(比方对象)的被援用次数保存起来,当被援用次数为零时开释。该办法的局限性:当呈现循环援用时,相互援用的对象不会被回收。 3. V8 垃圾回收机制V8 中有两个垃圾收集器。次要的 GC 应用 Mark-Compact 垃圾回收算法,从整个堆中收集垃圾。小型 GC 应用 Scavenger 垃圾回收算法,收集新生代垃圾。 两种不同的算法应答不同的场景: 应用 Scavenger 算法次要解决存活周期短的对象中的可拜访对象。应用 Mark-Compact 算法次要解决存活周期长的对象中的不可拜访的对象。因为新生代中存活的可拜访对象占多数,老生代中的不可拜访对象占多数,所以这两种回收算法配合应用非常高效。 3.1 分代垃圾收集在 V8 中,所有的 JavaScript 对象都通过堆来调配。V8 将其治理的堆分成两代:新生代和老生代。其中新生代又可细分为两个子代(Nursery、Intermediate)。 即新生代中的对象为存活工夫较短的对象,老生代中的对象为存活工夫较长或常驻内存的对象。 3.2 Mark-Compact 算法(Major GC)Mark-Compact 算法能够看作是 Mark-Sweep(标记革除)算法和 Cheney 复制算法的联合。该算法次要分为三个阶段:标记、革除、整顿。 ...

September 19, 2021 · 1 min · jiezi

关于gc:GC频繁抖动的主要原因

内存抖动内存抖动是因为大量的对象被创立又在短时间内马上被开释,如循环中调配对象,很容易引起GC,特地是在较大的循环次数或者一个循环中调配较多的长期对象时。 霎时产生大量的对象霎时产生大量的对象,即便对象不大,也可能使得堆的可用空间达到阈值登程GC或导致堆的扩容产生GC。 调配大内存块的对象调配大内存块的对象,如图片,大的byte数组等,尽管堆残余内存空间足够,然而依然可能登程GC,因为内存碎片导致了找不到间断空间来调配这大内存,从而登程GC。 内存泄露内存泄露,会导致可用内存越来越少,而且导致碎片也可能越来越重大,这样就更加容易登程GC。

July 14, 2021 · 1 min · jiezi

关于gc:垃圾回收GC

工作原理在面向对象编程中,每个类型都代表一种可供程序应用的资源。要应用这些资源必须为代表资源的类型分配内存。拜访一个资源所须要的步骤如下: 调用IL指令newobj,为代表资源的类型分配内存。在C#中应用new关键字,编译器会主动生成该指令初始化内存,设置资源的初始状态,使资源可用。类型的实例结构器负责初始状态拜访类型的成员来应用资源捣毁资源的状态以进行清理开释内存,由垃圾回收负责这看似简略的步骤,却频频引发编程谬误,如应用了已被开释的内存和没有开释不再须要的内存。 在非托管编程中,这种bug会造成内存透露(节约内存)和对象损坏的问题,如何正确的进行内存治理便是一个很重要的问题,而垃圾回收便是专门负责这一性能的。 从托管堆分配内存CLR要求所有资源都从托管堆调配,对象在应用程序不须要应用时便会被主动删除。 过程初始化时,CLR要保留一块间断的、最后并没有对应的物理存储空间地址空间,这个地址空间就是托管堆。托管堆保护着一个指向下一个对象在堆中调配的地位的指针NextObjPtr。刚开始的时候NextObjPtr设置为保留地址空间的基地址。 IL指令newobj用于创立一个对象,该指令会让CLR执行以下步骤: 计算类型以及所有基类型的字段须要的字节数加上对象开销所须要的字节数,每个对象都具备的两个开销字段:类型对象指针和一个同步索引块。对于32位应用程序两个字段各占32位须要8字节,64位应用程序两个字段各占64位须要16字节CLR查看保留区域是否可能提供调配对象所需的字节数,如果有必要就提交存储。如果托管堆有足够的空间,对象会被放入,对象将会放在NextObjPtr指针指向的地址中,并且为它调配的字节将会被清零。接着调用类型的实例结构器位this参数传递NextObjPtr,newobj指令将返回对象的地址。在地址返回前,NextObjPtr指针的值会加上对象占据的字节数从而失去一个新值,它指向下一个对象放入托管堆的地址 垃圾回收器工作原理应用程序调用new操作符创建对象时,可能存在没有足够的内存空间来调配该对象的状况,托管堆将对象须要的字节数加到NextObjPtr指针中的地址上来检测这种状况,如果后果值超过了地址空间的开端,表明托管堆已满,此时必须执行一次垃圾回收。 垃圾回收算法垃圾回收器查看应用程序中是否存在不再应用的对象,如果存在,它们应用的内存就能够回收,如果一次垃圾回收之后,堆中任然没有可用的内存,new操作符将会抛出一个OutOfMemoryException异样。 应用程序的root每个应用程序都蕴含一组根,每个根都是一个存储地位,其中蕴含一个指向援用类型对象的一个指针。该指针要么援用托管堆中的一个对象,要么为null *只有援用类型的变量才被认为是根,值类型的变量永远不认为是根 垃圾回收器如何晓得应用程序正在应用一个对象?标记阶段:垃圾回收器在开始执行时,总是假如堆中所有的对象都是垃圾。垃圾回收器沿着线程栈上行查看所有的根,如果发现一个根援用了一个对象,就在对象的同步索引字段上设置一个bit。例如应用程序的根间接援用了A,B,C对象,所有这些对象都被标记。在标记对象C的时候发现对象C援用了对象D的一个字段,造成对象D也被标记。垃圾回收器就是这样以递归的形式遍历所有可达对象。标记好根和它的援用对象后,垃圾回收器查看下一个根,并持续标记对象,如果试图标记一个先前曾经标记过的对象,就会沿着这个门路走上来,不会进行二次遍历查看好所有根之后,堆中将蕴含一组已标记和未标记的对象。已标记的对象是通过利用程序代码可达的对象,而未标记的对象是不可达的认为是垃圾,它们占用的内存能够回收。垃圾回收器将进入压缩阶段压缩阶段:垃圾回收器线性地遍历堆,寻找未标记对象的间断内存块,如果发现的内存块较小,垃圾回收器就将其疏忽。然而如果发现大的可间断的内存块,垃圾回收器就会把非垃圾对象挪动到这里以压缩堆。挪动内存中的对象之后,蕴含指向这些对象的指针的变量和CPU寄宿器都将有效。垃圾回收器必须从新拜访应用程序的所有根,并批改它们使其指向对象的新的内存地位。如果对象中的字段指向的是一个曾经挪动了地位的对象,垃圾回收器也要负责改过这些字段。堆内存压缩之后,NextObjPtr指针指向紧接着最初一个非垃圾对象之后的地位*由此可见,垃圾回收将造成显著的性能损失 应用终结操作开释本地资源大多数类型只须要内存便可失常工作,然而还有一些类型除了要应用类型还要应用本地资源,如FileStream类型。终结是CLR提供的一种机制,容许对象在垃圾回收器回收其内存之前执行一些清理操作。这些类型都实现了一个Finalize办法,当垃圾回收器断定一个对象是垃圾时,会调用对象的Finalize办法。 Finalize办法的定义 class People{ //这是一个Finalize办法 ~People() { //这里的代码会进入Finalize办法 }}通过ildasm检查程序集可确认Finalize办法已生成 CriticalFinalizerObject类型首次结构任何CriticalFinalizerObject派生类型的一个对象时,CLR立刻对继承层次结构中的所有Finalize办法进行JIT编译,在结构对象就编译这些办法可确保对象被断定为垃圾的时候本地资源能保障失去开释CLR会先调用非CriticalFinalizerObject派生类性的Finalize办法,再调用CriticalFinalizerObject派生类型的Finalize办法。如此托管类资源便能够在它们的Finalize办法中胜利的拜访CriticalFinalizerObject派生类型的对象如果AppDomain被一个宿主应用程序强行中断,CLR也会调用CriticalFinalizerObject派生类型的Finalize办法。宿主应用程序不再信赖它外部运行的托管代码时也利用这个性能确保本地资源得以开释引起Finalize办法调用的起因第0代满时,会主动触发垃圾回收,导致Finalize办法被调用的最常见的起因代码显示调用System.GC的静态方法Collect,不倡议这样操作Windows提醒内存不足CLR卸载AppDomain,卸载时CLR会认为AppDomain中不再存在任何根,因而会对所有代的对象执行垃圾回收CLR敞开,一个过程失常终止时,CLR就会敞开。在敞开过程中,CLR会认为该过程中不再存在任何根,因而会调用托管堆中的所有对象的Finalize办法。此时因为整个过程都要终止,CLR不会尝试压缩或开释内存,将有Windows负责回收过程的所有内存应用Finalize办法造成的性能影响可终结的对象要花更长的工夫分配内存,因为指向它们的指针必须放到终结列表中可终结对象在回收时必须进行一些额定的解决,导致程序的运行速度变慢Finalize办法揭秘应用程序创立新对象时,new操作符会从堆中分配内存,如果对象定义了Finalize办法,那么在该类的实例结构器被调用之前,会将指向该对象的一个指针放到一个终结列表。终结列表是由垃圾回收器管制的一个外部数据结构,列表中的每一项都指向一个对象。在回收该对象之前应该调用它的Finalize办法。 *结构一个类型的实例时,如果该类型的Finalize办法是从Object继承的,就不认为这个对象是能够终结的。类型必须重写Object的Finalize办法,这个类型及其派生类的对象才被人为是能够终结的。 垃圾回收器开始时,会查找终结列表中指向垃圾对象的指针。找到一个指针后,该指针会从终结列表中移除,并追加到垃圾回收器的另一个外部数据结构freachable队列中,freachable队列中的每个指针都代表其Finalize办法曾经筹备好的一个对象。 一个非凡的高优先级CLR线程专门负责Finalize办法的调用,当freachable队列为空时,这个线程将会休眠。一旦队列不为空,该线程便会被唤醒,将每一项从freachable队列中移除,并调用每个对象的Finalize办法。 freachable队列垃圾回收器会将不可达的对象视为垃圾,然而,当垃圾回收器将对象的援用从终结列表挪动到freachable队列后,对象将不再被认为是垃圾,其内存不可被回收。标记freachable对象时,这些对象的援用类型的字段所援用的对象也会被递归标记。所有这些对象都会在垃圾回收的过程中存活下来。这时,垃圾回收器完结对垃圾的标识(这个过程中会有某一些被认为是垃圾的对象又被从新认为不是垃圾)。而后垃圾回收器开始压缩可回收内存,非凡的CLR线程清空freachable队列,并执行每个对象的Finalize办法。垃圾回收器下一次调用时,会发现已终结的对象成为真正的垃圾,因为应用程序的根不再指向它,freachable队列也不再指向它。所以这些对象的内存会间接回收。*整个过程中,可终结的对象须要执行两次垃圾回收能力开释它们占用的内存。 FInalize办法蕴含共享状态的代码应该应用线程同步锁,在只有一个终结线程的状况下,可能存在多个CPU调配可终结的对象,但只有一个线程执行Finalize办法,会造成该线程可能跟不上调配速度,造成性能和伸缩性方面的问题。 using语句static void Main(){ using (FileStream fs = new FileStream("Temp.txt", FileMode.Create)) { fs.Write(new byte[] { 1, 2, 3 }, 0, 3); } File.Delete("Temp.txt");}应用using语句的时候编译器会主动生成一个try块和一个Finally块。显然,在Finally块中,编译器会生成代码将变量转型成一个IDisposable并调用Dispose办法。所以using语句只能用于实现了IDisposable接口的类型。 手动监督和管制对象的生存期GC Handle tableCLR为每个AppDomain都提供了一个GC句柄表,该表容许程序监督对象的生存期,或手动管制对象的生存期。在一个AppDomain创立之初,句柄表是空的。句柄表的每个记录项都蕴含两种信息:一个指向托管堆上一个对象的指针;一个flag标记,它指出想要监督或管制的对象。 应用GCHandle的Alloc办法管制或监督对象的生存期 public static GCHandle Alloc(object value, GCHandleType type);GCHandleType枚举 Weak:容许监督对象的生存期。可检测垃圾回收在什么时候断定该对象在利用程序代码中行将不可达。此时对象的Finalize可能曾经执行也可能没有执行,对象可能依然存在内存中。WeakTrackResurrection:容许监督对象的生存期。可检测垃圾回收在什么时候断定该对象在利用程序代码中行将不可达。此时对象的Finalize曾经执行,对象的内存已回收。Normal:容许管制对象的生存期。通知垃圾回收器:行将应用应用程序中没有变量援用的对象,该对象必须保留在内存中。垃圾回收产生时,该对象的内存能够压缩(挪动)。如果不向Alloc传递任何GCHandleType标记,就默认应用GCHandleType.Normal标记。Pinned:容许管制对象的生存期。通知垃圾回收器:行将应用应用程序中没有变量援用的对象,该对象必须保留在内存中。垃圾回收产生时,该对象的内存不能压缩(挪动)。须要将内存地址传给非托管代码时,这个标记就十分有用。非托管代码能够释怀的向托管代码的这个内存写入,晓得托管对象的地位不会因为垃圾回收而挪动。调用Alloc办法时,扫描AppDomain的GC句柄表,查找一个可用的记录项来存储传给Alloc的对象地址,并将标记设置为GCHandleType实参传递的值。接着返回GCHandle实例,该实例是一个轻量级的值类型,其中蕴含一个实例字段,它援用了句柄表中的记录索引项。能够通过获取GCHandle实例,调用其Free办法开释GC句柄表中的记录项,使GCHandle实例有效。 垃圾回收器如何应用GC句柄表垃圾回收器标记所有可达对象,而后扫描GC句柄表。所有Normal和Pinned都被看成时根,同时标记这些对象(包含这些对象的字段援用的对象)垃圾回收器扫描GC句柄表,查找所有Weak记录项。如果一个Weak记录项援用了一个未标记的对象,指针标识的就是一个垃圾对象,记录项的指针更改为null垃圾回收器扫描中结列表,如果列表中的一个指针未援用标记的对象,指针标识的就是一个不可达对象,指针将从终结列表移入freachable队列。这时对象将会被标记,因为对象又变成了可达对象垃圾回收器扫描GC句柄表,查找所有WeakTrackResurrection记录项。如果一个WeakTrackResurrection记录项援用了一个未标记的对象(由freachable队列中的一个记录项指向的一个对象),指针标识的就是一个垃圾对象,该记录项的指针值更改为null垃圾回收器对内存进行压缩,其实就是内存碎片整顿的过程。某些状况下如果垃圾回收器判断内存碎片化不重大,就会决定不压缩内存。Pinned对象是不会压缩(挪动)的,所以垃圾回收器会将其它对象移到它的四周托管代码的援用传给非托管代码要应用Pinned标记,因为非托管代码要回调托管代码时,不能真正的将指向一个托管对象的指针传给非托管代码,因为如果产生垃圾回收,对象可能在内存中挪动,指针便有效了。为了失常工作,调用Alloc办法,向它传递对象援用和Pinned标记,而后将返回的GCHandle实例转型成为一个Intptr,再将Intptr传递给非托管代码。非托管代码回调托管代码时,托管代码将Intptr转型成为GCHandle,查问Target属性取得托管对象的援用,非托管代码不再须要这个援用后能够调用GCHandle实例的Free办法,使将来的垃圾回收能开释这些对象。 对象复活一个被视为垃圾的对象又从新被当做可达(非垃圾)对象的过程,成为对象复活。垃圾回收器将一个对象的援用放入freachable队列,对象就变成可达对象了。待对象的Finalize办法返回,不再有根指向对象,对象才真正死亡。 Finalize办法在执行时将对象指针放到一个动态字段中 class Program{ public static Object s_Obj = null;}class SomeType{ ~SomeType() { Program.s_Obj = this; }}上述代码展现了当SomeType的Finalize办法被调用时,该对象的援用会被放入到一个根,使对象得以复活,应用程序能够自在应用这个对象。但须要留神的是,这个对象已经被终结,所以应用它可能造成无奈预测的后果。如果SomeType的一些字段援用了其它对象,这些对象都会被复活,在这些对象中一部分对象的Finalize办法曾经被调用过了。 ...

December 31, 2020 · 1 min · jiezi

关于gc:GC-垃圾回收机制认识与算法详解

目录GC相干概念常见GC算法援用计数算法 核心思想实现原理实例优缺点标记革除算法 核心思想实现原理图示优缺点标记整顿算法 核心思想实现原理图示优缺点GC相干概念[x] GC:垃圾回收机制的简写,垃圾回收期实现具体的工作.能够找到内存中的垃圾、并开释和回收空间[x] GC垃圾:程序中 不再须要应用的,程序中不能再拜访的 对象[x] GC算法:是GC工作时查找和回收所遵循的规定常见GC算法援用计数标记革除标记整顿分待回收(V8用到的)援用计数算法核心思想设置援用数,判断以后援用数是否为0来决定是不是垃圾对象,如果是0就GC就进行工作,进行回收。 实现原理援用计数器援用关系扭转时批改援用数字援用数字为0时立刻回收实例const user1 = { age: 11 }const user2 = { age: 12 }const user3 = { age: 13 }const nameList = [user1.age, user2.age, user3.age]function fn() { const num1 = 1 const num2 = 2 num3 = 3}fn()当函数调用过后,num1和num2在内部不能应用,援用数为0,会被回收 ; num3是挂载在window上的,所以不会被回收 ; 下面的user1、user2、user3被nameList援用,所以援用数不为0不会被回收 ;优缺点援用计数算法内容长处1. 发现垃圾时立刻回收<br/>2. 最大限度缩小程序暂停,让空间不会有被占满的时候毛病1. 无奈回收循环援用的对象<br/>2. 资源耗费开销大(对所有对象进行数值的监控和批改,自身就会占用工夫和资源)上面举一栗子阐明下面毛病中无奈回收循环利用对象的状况: function fn() { const obj1 = {} const obj2 = {} obj1.name = obj2 obj2.name = obj1 return 'hello world'}fn()// obj1和obj2,因为相互有援用,所以计数器并不为0,fn调用之后仍旧无奈回收这两个对象标记革除算法相比原理实现更加简略,还能解决相应问题,V8当中会大量应用到。 ...

December 12, 2020 · 1 min · jiezi

关于gc:面试官问如何排除GC引起的CPU飙高我脱口而出5个步骤

关注“Java后端技术全栈” 回复“000”获取大量电子书 在工作中,当一个零碎产生OOM的时候,这种问题可能会让大家很懊恼困惑,因为故障排查起来是一个综合技术的考量。在平时工作中要减少本人的常识广度,多学习,多总结,多思考,多做笔记,这才是真正的王道。 尤其是在线上环境中,如何剖析是哪个线程导致的CPU飙高的问题,通常大抵有几个差不多固定的步骤。这个问题也是面试频率十分之高的问题之一,很多人也是靠答复这个问题而加薪。 常见套路步骤: 应用top命令应用top -H pid 应用prinf %x tid 应用jsack pid >pid.log 查阅less pid.log 上面就来说一下这几个步骤。 top咱们能够应用top命令来查找对应应用CPU最多的过程,找到后,先记录下对应的pid(前面要用到)。 再应用Shift+P这两个快捷键能够按CPU的使用率进行排序。 top-H pid再次应用top名,然而这次减少一个参数-H,能够查看下面找进去的pid过程中对应的线程tid,记住这时候的线程tid得记住 printf然而此时的tid是十进制的,咱们须要把这个tid转成16进制。而后应用 printf %x tid stack应用jstack工具把线程信息输入到对应的日志文件中,前面应用这个日志文件内容进行剖析。 jstack pid >pid.log less下面曾经生成日志文件了,这时候能够应用less命令来查找下面曾经转换好的16进制的线程tid。 less pid.log 其它另外还能够应用 jstat -gcutil pid 1000 10 来查看垃圾回收的实时状况。 对于jstat的应用前参考后面的文章 工具。 举荐一篇很全的线上CPU飙高的解决文章 http://woaijava.cc/blog/20111... 举荐浏览: 《Spring Cloud与Docker微服务架构实战》.pdf 《Go语言实战》.pdf 《分布式Java利用根底与实际》.pdf

November 20, 2020 · 1 min · jiezi

关于gc:面试重灾区JVM内存结构和垃圾回收机制

JVM介绍1. JVM的体系架构(内存模型) 绿色的为线程公有,橘色的为线程共有 2. 类加载器负责将.class文件加载到内存中,并且将该文件中的数据结构转换为办法区中的数据结构,生成一个Class对象 2.1 类加载器分类自启动类加载器。Bootstrap ClassLoader类加载器。负责加载jdk自带的包。 %JAVA_HOME%/lib/rt.jar%即JDK源码应用C++编写在程序中间接获取被该加载器加载的类的类加载器会呈现null扩大类加载器.Extension ClassLoader。负责加载jdk扩大的包 便于将来扩大%JAVA_HOME/lib/ext/*.jar%利用类加载器或零碎类加载器。AppClassLoader或SystemClassLOader 用于加载自定义类的加载器CLASSPATH门路下自定义类加载器 通过实现ClassLoader抽象类实现2.2 双亲委派机制当利用类加载器获取到一个类加载的申请的时候,不会立刻解决这个类加载申请,而是将这个申请委派给他的父加载器加载,如果这个父加载器不可能解决这个类加载申请,便将之传递给子加载器。一级一级传递领导能够加载该类的类加载器。 该机制又称沙盒平安机制。避免开发者对JDK加载做毁坏 2.3 突破双亲委派机制自定义类加载器,重写loadClass办法应用线程上下文类加载器2.4 Java虚拟机的入口文件sun.misc.Launcher类 3. Execution Engine执行引擎负责执行解释命令,交给操作系统进行具体的执行 4. 本地接口4.1 native办法native办法指Java层面不能解决的操作,只能通过本地接口调用本地的函数库(C函数库) 4.2 Native Interface一套调用函数库的接口 5. 本地办法栈在加载native办法的时候,会将执行的C函数库的办法,放在这个栈区域执行 6. 程序计数器每个线程都有程序计数器,次要作用是存储代码指令,就相似于一个执行打算。 外部保护了多个指针,这些指针指向了办法区中的办法字节码。执行引擎从程序计数器中获取下一次要执行的指令。 因为空间很小,他是以后线程执行代码的一个行号指示器/ 不会引发OOM 7. 办法区供各线程共享的运行时内存区域,寄存了各个类的构造信息(一个Class对象),包含:字段,办法,构造方法,运行时常量池。 尽管JVM标准将办法区形容为堆的一个逻辑局部,但它却还有一个别名叫做Non-Heap(非堆),目标就是要和堆离开 次要有:永恒代或者元空间。存在GC 元空间中因为间接应用物理内存的影响,所以默认的最大元空间大小为1/4物理内存大小 8. Java栈次要负责执行各种办法,是线程公有的,随线程的沦亡而沦亡,不存在垃圾回收的问题。八大数据类型和实例援用都是在函数的栈内存中分配内存的。 默认大小为512~1024K,通过-Xss1024k参数批改 8.1 栈和队列数据结构栈FILO:先进后出 队列FIFO:先进先出 8.2 存储的数据本地变量Local Variable。包含办法的形参和返回值栈操作Operand Stack。包含各种压栈和出栈操作栈帧数据Frame Data。就相当于一个个办法。在栈空间中,办法被称为栈帧8.3 执行流程栈中执行的单位是栈帧,栈帧就是一个个办法。 首先将main办法压栈,成为一个栈帧而后调用其余办法,即再次压栈栈帧中存储了这个办法的局部变量表,操作数栈、动静链接、办法进口等栈的大小和JVM的实现无关,通常在256K~756K 9. 办法区,栈,堆的关系 10. Heap 堆10.1 堆内存构造默认初始大小为物理内存的1/64,默认最大大小为1/4。在理论生产中个别会将这两个值设置为雷同,防止垃圾回收器执行完垃圾回收当前还须要进行空间的扩容计算,浪费资源。 堆外内存:内存对象调配在Java虚拟机的堆以外的内存,这些内存间接受操作系统治理(而不是虚拟机),这样做的后果就是可能在肯定水平上缩小垃圾回收对应用程序造成的影响。应用未公开的Unsafe和NIO包下ByteBuffer来创立堆外内存。 默认的堆外内存大小为,通过-XX:MaxDirectMemorySize=执行堆外内存的大小 10.1.1 JDK1.7 在逻辑上划分为三个区域: 新生区Young Generation Space。 ...

November 10, 2020 · 2 min · jiezi

关于gc:javascript垃圾回收及性能优化如何调优测试-123

内存治理内存为什么须要治理 function fn(){ arrlist=[]; arrlist[100000]='lg is a coder';}fn(); 不当的操作会导致堆内存,飙升。

November 8, 2020 · 1 min · jiezi

关于gc:03JVM垃圾回收机制

后面在jvm组成构造一文中,说到了GC和一些算法,那么在这篇文章里,就具体说说GC的罕用算法。垃圾回收(Garbage Collection,GC),就是将垃圾回收,防止过于占用内存空间,导致内存透露,对内存堆中曾经死亡或者长时间没有应用的对象进行革除和回收。既然是垃圾回收,那么如何判断哪些对象是垃圾,须要被回收呢? 如何找到程序里的垃圾(1)援用计数法援用计数法其实就是给每个对象增加一个计数器RC,当有中央援用该对象时计数器加1,当援用生效时计数器减1。通过对象计数器是否为0来判断对象是否可被回收。然而相应的,它无奈解决循环援用的问题。倘若如下:定义一个字符串 String str = new String ("tony");其实这个时候,str援用了字符串"tony",那么这个字符串援用次数就是1,这时候如果m指向了另一个新的值,例如 m=null,那这个时候"tony"的援用次数就成了0了,也就意味着要被回收了。 public class ReferenceCountingGC { public Object instance; public ReferenceCountingGC(String name) { } public static void testGC(){ ReferenceCountingGC a = new ReferenceCountingGC("objA"); ReferenceCountingGC b = new ReferenceCountingGC("objB"); // a和b相互援用了 a.instance = b; b.instance = a; a = null; b = null; }}从代码中能够看出,a,b这两个对象始终在相互援用,如果采纳援用计数法的话,是永远无奈GC它们的,因为它们的援用计数永远都不会为0,所以说援用计数法是有局限性的,或者说它不属于严格意义上的垃圾收集机制。 (1)可达性剖析算法GC ROOT是什么? 首先咱们晓得标记算法,JVM的标记算法咱们能够理解为一个可达性算法,所以所有的可达性算法都会有终点,那么这个终点就是GC Root。也就是须要通过GC Root 找出所有活的对象,那么剩下所有的没有标记的对象就是须要回收的对象。通过 GC ROOT的对象作为搜寻起始点,通过援用向下搜寻,所走过的门路称为援用链。通过对象是否有达到援用链的门路来判断对象是否可被回收(可作为GC ROOT的对象:虚拟机栈中援用的对象,办法区中类动态属性援用的对象,办法区中常量援用的对象,本地办法栈中JNI援用的对象)通过可达性算法,胜利解决了援用计数所无奈解决的循环依赖问题,只有你无奈与GC Root建设间接或间接的连贯,零碎就会断定你为可回收对象。那这样就引申出了另一个问题,哪些属于GC Root。Java内存区域中哪些能够作为GC Root对象?虚拟机栈中援用的对象public class StackLocalParameter { public StackLocalParameter(String name) {} public static void testGC() { StackLocalParameter s = new StackLocalParameter("localParameter"); s = null; }}办法区中类动态属性援用的对象public class MethodAreaStaicProperties { public static MethodAreaStaicProperties m; public MethodAreaStaicProperties(String name) {} public static void testGC(){ MethodAreaStaicProperties s = new MethodAreaStaicProperties("properties"); s.m = new MethodAreaStaicProperties("parameter"); s = null; }}办法区中常量援用的对象public class MethodAreaStaicProperties { public static final MethodAreaStaicProperties m = MethodAreaStaicProperties("final"); public MethodAreaStaicProperties(String name) {} public static void testGC() { MethodAreaStaicProperties s = new MethodAreaStaicProperties("staticProperties"); s = null; }}本地办法栈中援用的对象任何native接口都会应用某种本地办法栈,实现的本地办法接口是应用C连贯模型的话,那么它的本地办法栈就是C栈。当线程调用Java办法时,虚构机会创立一个新的栈帧并压入Java栈。然而当它调用的是本地办法时,虚构机会放弃Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简略地动静连贯并间接调用指定的本地办法。 ...

October 16, 2020 · 1 min · jiezi

Java-中的强引用软引用弱引用虚引用以及-GC-策略

在介绍各种引用之前,先简单介绍下垃圾回收 什么是垃圾回收垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,而此时存在一个很大的矛盾,C++ 等语言创建对象要不断的去开辟空间,不用的时候又需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的 allocated,然后不停的析构。于是,有人就提出,能不能写一段程序实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢?1960年,基于 MIT 的 Lisp 首先提出了垃圾回收的概念,用于处理C语言等不停的析构操作,而这时 Java 还没有出世呢!所以实际上 GC 并不是Java的专利,GC 的历史远远大于 Java 的历史!Java中的垃圾回收是根据可达性分析算法来判断对象是否存活的 可达性分析算法在主流的商用程序语言(Java、C#,甚至包括前面提到的古老的Lisp)的主流实现中,都是称通过可达性分析(Reachability Analysis)来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如图3-1所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。 在Java语言中,可作为GC Roots的对象包括下面几种: 虚拟机栈(栈帧中的本地变量表)中引用的对象。方法区中类静态属性引用的对象。方法区中常量引用的对象。本地方法栈中JNI(即一般说的Native方法)引用的对象。各种引用对象是否存活与“引用”有关。 在JDK 1.2以前,Java中的引用的定义很传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,对于如何描述一些“食之无味,弃之可惜”的对象就显得无能为力(这里说的就是强引用,最基本的引用方式)。 我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。 在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。 引用类型GC策略简介强引用(Strong Reference)永远不会回收(GC ROOT可引用到的前提下)最基本的引用Object obj=new Object()软引用(Soft Reference)OOM之前回收SoftReference弱引用(Weak Reference)下一次GC前WeakReference虚引用(Phantom Reference)未知,也就是随时可能被回收PhantomReference强引用强引用就是最基本的引用方式,Object obj=new Object(),引用的是另一块内存的起始地址。强引用的对象回收基于“可达性分析”算法,当对象不可达时才可能会被回收。 比如方法中new的对象,引用赋值给方法内的局部变量(局部变量存储在栈帧中的局部变量表),当方法结束之后,栈帧出栈,对象就自然不可达了,不可达就可能会被回收 软引用软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。SoftReference<RefObj> ref = new SoftReference<RefObj>(refObj);写个例子来测试下软引用的GC策略: # JVM OPTIONS: -XX:+PrintGCDetails -Xmx5mpublic class ReferenceTest { private List<RefObj> refObjs = new ArrayList<>(); private SoftReference<RefObj> ref = new SoftReference<RefObj>(createRefObj(4096*256));//1m public void add(){ refObjs.add(createRefObj(4096)); } private RefObj createRefObj(int dataSize){ RefObj refObj = new RefObj(); byte[] data = new byte[dataSize]; for (int i = 0; i < dataSize; i++) { data[i] = Byte.MAX_VALUE; } refObj.setData(data); return refObj; } public void validRef(){ System.out.println(ref.get()); } public static void main(String[] args) { ReferenceTest referenceTest = new ReferenceTest(); for (int i = 0; i < 1200; i++) { //不停新增堆大小 referenceTest.add(); //新增后查看SoftReference中的对象是否被回收 referenceTest.validRef(); } } private class RefObj{ private byte[] data; public byte[] getData() { return data; } public void setData(byte[] data) { this.data = data; } }}ReferenceTest中维护一个RefObjList和一个SoftReference,往RefObjList不断添加对象,增加堆大小,直至内存溢出。来观察下SoftReference中引用的对象是否还存在 ...

October 7, 2019 · 4 min · jiezi

Golang-中使用-Slice-索引-Map-替代-Map-获得性能提升

起因在我们的多个线上游戏项目中,很多模块和服务为了提高响应速度,都在内存中存放了大量的(缓存)数据以便获得最快的访问速度。 通常情况下,为了使用方便,使用了 go 自身的 map 作为存放容器。当有超过几十万 key 值,并且 map 的 value 是一个复杂的 struct 时,额外引入的 GC 开销是无法忽视的。在 cpu 使用统计图中,我们总是观测到较为规律的短时间峰值。这个峰值在使用 1.3 版本的 go 中显得特别突出(stop the world 问题)。后续版本 go gc 不断优化,到我们现在使用的 1.10 已经是非常快速的并发 gc 并且只会有很短暂的 stw。 不过在各种 profile 的图中,我们依然观察到了大量的 runtime.scanobject 开销! 在一个14年开始的讨论中,就以及发现了 大 map 带来(特别是指针作为 value 的 map)的 gc 开销。遗憾的是在 2019 年的今天这个问题仍然存在。 在上述的讨论帖子中,有一个 Contributor randall77 提到: Hash tables will still have overflow pointers so they will still need to be scanned and there are no plans to fix that.不明白他的 overflow pointers 指的什么,但是看起来如果你有一个大的,指针作为 value 的 map 时,gc 的 scanobject 耗时就不会少。 ...

September 9, 2019 · 1 min · jiezi

程序员楼下闲聊某次jvm崩溃排查

大望路某写字楼下。猿A:上家公司的时候,我们组那个项目,每天半夜会跑个大批量数据处理的定时任务,然后程序经常崩溃。我:哦?那怎么处理的猿A:当时的架构有点水,说让调整“伊甸园”和“from-to”的比例……崩溃和这个就没关系我:少年,你成功引起了我的注意。来来来,请你喝饮料,好好聊聊当时的情况。业务场景“猿A”是我的同事兼死党,和他详聊后大概明白了当时的场景。 据我理解,那个定时任务,会从hive里拿出超多的数据(据说2亿左右),按具体业务做数据整合和处理,最终推送到es(elasticsearch)中。(hive什么的我没搞过,但不妨碍要讨论的东西) 业务处理部分,使用了线程池FixedThreadPool。 模拟解决过程问题定位猿A:那时候怀疑是内存OOM导致的jvm崩溃,进而怀疑大量对象GC回收不了,于是打了GC日志。我:嗯,没用hs_err_pid_xxx.log分析吗?猿A:当时小,还不会这个技能……死党“猿A”当时的解决过程比较粗暴,有了怀疑就直接在启动参数增加了-XX:+PrintGC。此命令会打印GC日志,姑且认为生产环境使用GC是CMS,写个demo模拟当时的场景。 public class CMSGCLogs { //启动参数:-Xmx10m -Xms10m -Xmn4M -XX:+PrintGC -XX:+UseConcMarkSweepGC public static void main(String[] args) throws InterruptedException { // 线程数设置为1,起名`T-1` ExecutorService es = Executors.newFixedThreadPool(1, new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r,"T-1"); } }); boolean flag = true; while (flag){ es.execute(()->{ try { byte[] bytes = new byte[1024*1000*1]; //模拟从hive中读取了大量数据(1M) TimeUnit.MILLISECONDS.sleep(50L); //模拟写入es过程 } catch (Exception e) { e.printStackTrace(); } }); } es.shutdown(); }}先背一段线程池的处理过程。 ...

September 9, 2019 · 3 min · jiezi

什么会导致Java应用程序的CPU使用率飙升

问题无限循环的while会导致CPU使用率飙升吗?经常使用Young GC会导致CPU占用率飙升吗?具有大量线程的应用程序的CPU使用率是否较高?CPU使用率高的应用程序的线程数是多少?处于BLOCKED状态的线程会导致CPU使用率飙升吗?分时操作系统中的CPU是消耗us还是sy?思路1.如何计算CPU使用率?CPU%= 1 - idleTime / sysTime * 100 idleTime:CPU空闲的时间sysTime:CPU处于用户模式和内核模式的时间总和2.与CPU使用率有关的是什么?人们常说,计算密集型程序的CPU密集程度更高。 那么,JAVA应用程序中的哪些操作更加CPU密集? 以下列出了常见的CPU密集型操作: 频繁的GC; 如果访问量很高,可能会导致频繁的GC甚至FGC。当调用量很大时,内存分配将如此之快以至于GC线程将连续执行,这将导致CPU飙升。序列化和反序列化。稍后将给出一个示例:当程序执行xml解析时,调用量会增加,从而导致CPU变满。序列化和反序列化;正则表达式。 我遇到了正则表达式使CPU充满的情况; 原因可能是Java正则表达式使用的引擎实现是NFA自动机,它将在字符匹配期间执行回溯。我写了一篇文章“ 正则表达式中的隐藏陷阱 ”来详细解释原因。线程上下文切换; 有许多已启动的线程,这些线程的状态在Blocked(锁定等待,IO等待等)和Running之间发生变化。当锁争用激烈时,这种情况很容易发生。有些线程正在执行非阻塞操作,例如while (true)语句。如果在程序中计算需要很长时间,则可以使线程休眠。3. CPU是否与进程和线程相关?现在,分时操作系统使用循环方式为进程调度分配时间片。如果进程正在等待或阻塞,那么它将不会使用CPU资源。线程称为轻量级进程,并共享进程资源。因此,线程调度在CPU中也是分时的。但在Java中,我们使用JVM进行线程调度。因此,通常,线程调度有两种模式:时间共享调度和抢占式调度。 答案1. while的无限循环会导致CPU使用率飙升吗?是。 首先,无限循环将调用CPU寄存器进行计数,此操作将占用CPU资源。那么,如果线程始终处于无限循环状态,CPU是否会切换线程? 除非操作系统时间片到期,否则无限循环不会放弃占用的CPU资源,并且无限循环将继续向系统请求时间片,直到系统没有空闲时间来执行任何其他操作。 stackoverflow中也提出了这个问题:为什么无意的无限循环增加了CPU的使用? https://stackoverflow.com/questions/2846165/why-does-an-infinite-loop-of-the-unintended-kind-increase-the-cpu-use 2.频繁的Young GC会导致CPU占用率飙升吗?是。 Young GC本身就是JVM用于垃圾收集的操作,它需要计算内存和调用寄存器。因此,频繁的Young GC必须占用CPU资源。 让我们来看一个现实世界的案例。for循环从数据库中查询数据集合,然后再次封装新的数据集合。如果内存不足以存储,JVM将回收不再使用的数据。因此,如果所需的存储空间很大,您可能会收到CPU使用率警报。 3.具有大量线程的应用程序的CPU使用率是否较高?不时。 如果通过jstack检查系统线程状态时线程总数很大,但处于Runnable和Running状态的线程数不多,则CPU使用率不一定很高。 我遇到过这样一种情况:系统线程的数量是1000+,其中超过900个线程处于BLOCKED和WAITING状态。该线程占用很少的CPU。 但是大多数情况下,如果线程数很大,那么常见的原因是大量线程处于BLOCKED和WAITING状态。 4.对于CPU占用率高的应用程序,线程数是否较大?不是。 高CPU使用率的关键因素是计算密集型操作。如果一个线程中有大量计算,则CPU使用率也可能很高。这也是数据脚本任务需要在大规模集群上运行的原因。 5.处于BLOCKED状态的线程是否会导致CPU占用率飙升?不会。 CPU使用率的飙升更多是由于上下文切换或过多的可运行状态线程。处于阻塞状态的线程不一定会导致CPU使用率上升。 6.如果分时操作系统中CPU的值us或sy值很高,这意味着什么?您可以使用命令查找CPU的值us和sy值top,如以下示例所示: us:用户空间占用CPU的百分比。简单来说,高我们是由程序引起的。通过分析线程堆栈很容易找到有问题的线程。sy:内核空间占用CPU的百分比。当sy为高时,如果它是由程序引起的,那么它基本上是由于线程上下文切换。经验如何找出CPU使用率高的原因?下面简要描述分析过程。 如果发现应用程序服务器的CPU使用率很高,请首先检查线程数,JVM,系统负载等参数,然后使用这些参数来证明问题的原因。其次,使用jstack打印堆栈信息并使用工具分析线程使用情况(建议使用fastThread,一个在线线程分析工具)。 以下是一个真实案例: 一天晚上,我突然收到一条消息,说CPU使用率达到了100%。所以立即,我倾倒了用jstack打印的堆栈信息。 进一步检查日志: onsumer_ODC_L_nn_jmq919_1543834242875 - priority:10 - threadid:0x00007fbf7011e000 - nativeid:0x2f093 - state:RUNNABLEstackTrace:java.lang.Thread.State:RUNNABLEat java.lang.Object.hashCode(Native Method)at java.util.HashMap.hash(HashMap.java:362)at java.util.HashMap.getEntry(HashMap.java:462)at java.util.HashMap.containsKey(HashMap.java:449)at com.project.order.odc.util.XmlSerializableTool.deSerializeXML(XMLSerializableTool.java:100)at com.project.plugin.service.message.resolver.impl.OrderFinishMessageResolver.parseMessage(OrderFinishMessageResolver.java:55)at com.project.plugin.service.message.resolver.impl.OrderFinishMessageResolver.parseMessage(OrderFinishMessageResolver.java:21)at com.project.plugin.service.message.resolver.impl.AbstractResolver.resolve(AbstractResolver.java:28)at com.project.plugin.service.jmq.AbstractListener.onMessage(AbstractListener.java:44)现在通过这个日志找到了问题:用于反序列化MQ消息实体的方法导致CPU使用率飙升。 ...

May 24, 2019 · 1 min · jiezi

GC日志分析

查看默认的垃圾收集器类型$ jinfo -flags pid #打印所有JVM参数未自定义垃圾收集器的情况下在Non-default VM flags:一栏可以看到默认的 GC收集器设置为:-XX:+UseParallelGC。 -XX:+UseParallelGC 使用的组合收集器:Parallel Scavenge(新生代使用的收集器) + Serial Old(老年代使用的收集器) GC日志参数-XX:+PrintGC 打印GC日志,和 -verbose:gc 是相同的命令-XX:+PrintGCDetails 打印GC的详细日志-XX:+PrintGCTimeStamps 打印GC的时间戳(JVM启动到GC发生所经历的时间)-XX:+PrintGCDateStamps 打印GC的日期时间(如:2019-05-06T19:34:52.072+0800)-XX:+PrintHeapAtGC 打印GC前后的详细的堆信息-Xloggc:logs/gc.log.`date "+%Y-%m-%d"` GC日志输出到指定文件日志输出分析(点击查看原图):

May 6, 2019 · 1 min · jiezi

Node-内存管理和垃圾回收

前言从前端思维转变到后端, 有一个很重要的点就是内存管理。以前写前端因为只是在浏览器上运行, 所以对于内存管理一般不怎么需要上心, 但是在服务器端, 则需要斤斤计较内存。 V8的内存限制和垃圾回收机制内存限制内存限制一般的后端语言开发中, 在基本的内存使用是没有限制的。 但由于Node是基于V8构建的, 而V8对于内存的使用有一定的限制。 在默认情况下, 64位的机器大概可以使用1.4G, 而32则为0.7G的大小。关于为什么要限制内存大小, 有两个方面。一个是V8一开始是为浏览器服务的, 而在浏览器端这样的内存大小是绰绰有余的。另一个则是待会提到的垃圾回收机制, 垃圾回收会暂停Js的运行, 如果内存过大, 就会导致垃圾回收的时间变长, 从而导致Js暂停的时间过长。 当然, 我们可以在启动Node服务的时候, 手动设置内存的大小 如下: node --max-old-space-size=768 // 设置老生代, 单位为MB node --max-semi-space-size=64 // 设置新生代, 单位为MB查看内存 在Node环境中, 可以通过process.memoryUsage()来查看内存分配 rss(resident set size):所有内存占用,包括指令区和堆栈heapTotal:V8引擎可以分配的最大堆内存,包含下面的 heapUsedheapUsed:V8引擎已经分配使用的堆内存external: V8管理C++对象绑定到JavaScript对象上的内存事实上, 对于大文件的操作通常会使用Buffer, 究其原因就是因为Node中内存小的原因, 而使用Buffer是不受这个限制, 它是堆外内存, 也就是上面提到的external。 v8的内存分代目前没有一种垃圾自动回收算法适用于所有场景, 所以v8的内部采用的其实是两种垃圾回收算法。他们回收的对象分别是生存周期较短和生存周期较长的两种对象。关于具体的算法, 参考下文。 这里先介绍v8是怎么做内存分代的。 新生代 v8中的新生代主要存放的是生存周期较短的对象, 它具有两个空间semispace, 分别为From和To, 在分配内存的时候将内存分配给From空间, 当垃圾回收的时候, 会检查From空间存活的对象(广度优先算法)并复制到To空间, 然后清空From空间, 再互相交换From和To空间的位置, 使得To空间变为From空间。 该算法缺陷很明显就是有一半的空间一直闲置着并且需要复制对象, 但是由于新生代本身具有的内存比较小加上其分配的对象都是生存周期比较短的对象, 所以浪费的空间以及复制使用的开销会比较小。 在64位系统中一个semisapce为16MB, 而32位则为8MB, 所以新生代内存大小分别为32MB和16MB。 老生代 老生代主要存放的是生存周期比较长的对象。内存按照 1MB 分页,并且都按照 1MB 对齐。新生代的内存页是连续的,而老生代的内存页是分散的,以链表的形式串联起来。 它的内部有4种类型。 ...

May 5, 2019 · 3 min · jiezi

GO GC 垃圾回收机制

垃圾回收(Garbage Collection,简称GC)是编程语言中提供的内存管理功能。在传统的系统级编程语言(主要指C/C++)中,程序员定义了一个变量,就是在内存中开辟了一段相应的空间来存值。由于内存是有限的,所以当程序不再需要使用某个变量的时候,就需要销毁该对象并释放其所占用的内存资源,好重新利用这段空间。在C/C++中,释放无用变量内存空间的事情需要由程序员自己来处理。就是说当程序员认为变量没用了,就手动地释放其占用的内存。但是这样显然非常繁琐,如果有所遗漏,就可能造成资源浪费甚至内存泄露。当软件系统比较复杂,变量多的时候程序员往往就忘记释放内存或者在不该释放的时候释放内存了。这对于程序开发人员是一个比较头痛的问题。为了解决这个问题,后来开发出来的几乎所有新语言(java,python,php等等)都引入了语言层面的自动内存管理 – 也就是语言的使用者只用关注内存的申请而不必关心内存的释放,内存释放由虚拟机(virtual machine)或运行时(runtime)来自动进行管理。而这种对不再使用的内存资源进行自动回收的功能就被称为垃圾回收。垃圾回收常见的方法引用计数(reference counting)引用计数通过在对象上增加自己被引用的次数,被其他对象引用时加1,引用自己的对象被回收时减1,引用数为0的对象即为可以被回收的对象。这种算法在内存比较紧张和实时性比较高的系统中使用的比较广泛,如ios cocoa框架,php,python等。优点:1、方式简单,回收速度快。缺点:1、需要额外的空间存放计数。2、无法处理循环引用(如a.b=b;b.a=a这种情况)。3、频繁更新引用计数降低了性能。标记-清除(mark and sweep)该方法分为两步,标记从根变量开始迭代得遍历所有被引用的对象,对能够通过应用遍历访问到的对象都进行标记为“被引用”;标记完成后进行清除操作,对没有标记过的内存进行回收(回收同时可能伴有碎片整理操作)。这种方法解决了引用计数的不足,但是也有比较明显的问题:每次启动垃圾回收都会暂停当前所有的正常代码执行,回收是系统响应能力大大降低!当然后续也出现了很多mark&sweep算法的变种(如三色标记法)优化了这个问题。复制收集复制收集的方式只需要对对象进行一次扫描。准备一个「新的空间」,从根开始,对对象进行扫,如果存在对这个对象的引用,就把它复制到「新空间中」。一次扫描结束之后,所有存在于「新空间」的对象就是所有的非垃圾对象。这两种方式各有千秋,标记清除的方式节省内存但是两次扫描需要更多的时间,对于垃圾比例较小的情况占优势。复制收集更快速但是需要额外开辟一块用来复制的内存,对垃圾比例较大的情况占优势。特别的,复制收集有「局部性」的优点。在复制收集的过程中,会按照对象被引用的顺序将对象复制到新空间中。于是,关系较近的对象被放在距离较近的内存空间的可能性会提高,这叫做局部性。局部性高的情况下,内存缓存会更有效地运作,程序的性能会提高。对于标记清除,有一种标记-压缩算法的衍生算法:对于压缩阶段,它的工作就是移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的。分代收集(generation)这种收集方式用了程序的一种特性:大部分对象会从产生开始在很短的时间内变成垃圾,而存在的很长时间的对象往往都有较长的生命周期。根据对象的存活周期不同将内存划分为新生代和老年代,存活周期短的为新生代,存活周期长的为老年代。这样就可以根据每块内存的特点采用最适当的收集算法。 新创建的对象存放在称为 新生代(young generation)中(一般来说,新生代的大小会比 老年代小很多)。高频对新生成的对象进行回收,称为「小回收」,低频对所有对象回收,称为「大回收」。每一次「小回收」过后,就把存活下来的对象归为老年代,「小回收」的时候,遇到老年代直接跳过。大多数分代回收算法都采用的「复制收集」方法,因为小回收中垃圾的比例较大。这种方式存在一个问题:如果在某个新生代的对象中,存在「老生代」的对象对它的引用,它就不是垃圾了,那么怎么制止「小回收」对其回收呢?这里用到了一中叫做写屏障的方式。程序对所有涉及修改对象内容的地方进行保护,被称为「写屏障」(Write Barrier)。写屏障不仅用于分代收集,也用于其他GC算法中。在此算法的表现是,用一个记录集来记录从新生代到老生代的引用。如果有两个对象A和B,当对A的对象内容进行修改并加入B的引用时,如果①A是「老生代」②B是「新生代」。则将这个引用加入到记录集中。「小回收」的时候,因为记录集中有对B的引用,所以B不再是垃圾。三色标记算法三色标记算法是对标记阶段的改进,原理如下:起初所有对象都是白色。从根出发扫描所有可达对象,标记为灰色,放入待处理队列。从队列取出灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。重复 3,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。可视化如下。三色标记的一个明显好处是能够让用户程序和 mark 并发的进行,具体可以参考论文:《On-the-fly garbage collection: an exercise in cooperation.》。Golang 的 GC 实现也是基于这篇论文,后面再具体说明。GO的垃圾回收器go语言垃圾回收总体采用的是经典的mark and sweep算法。v1.3以前版本 STW(Stop The World)golang的垃圾回收算法都非常简陋,然后其性能也广被诟病:go runtime在一定条件下(内存超过阈值或定期如2min),暂停所有任务的执行,进行mark&sweep操作,操作完成后启动所有任务的执行。在内存使用较多的场景下,go程序在进行垃圾回收时会发生非常明显的卡顿现象(Stop The World)。在对响应速度要求较高的后台服务进程中,这种延迟简直是不能忍受的!这个时期国内外很多在生产环境实践go语言的团队都或多或少踩过gc的坑。当时解决这个问题比较常用的方法是尽快控制自动分配内存的内存数量以减少gc负荷,同时采用手动管理内存的方法处理需要大量及高频分配内存的场景。v1.3 Mark STW, Sweep 并行1.3版本中,go runtime分离了mark和sweep操作,和以前一样,也是先暂停所有任务执行并启动mark,mark完成后马上就重新启动被暂停的任务了,而是让sweep任务和普通协程任务一样并行的和其他任务一起执行。如果运行在多核处理器上,go会试图将gc任务放到单独的核心上运行而尽量不影响业务代码的执行。go team自己的说法是减少了50%-70%的暂停时间。v1.5 三色标记法go 1.5正在实现的垃圾回收器是“非分代的、非移动的、并发的、三色的标记清除垃圾收集器”。引入了上文介绍的三色标记法,这种方法的mark操作是可以渐进执行的而不需每次都扫描整个内存空间,可以减少stop the world的时间。 由此可以看到,一路走来直到1.5版本,go的垃圾回收性能也是一直在提升,但是相对成熟的垃圾回收系统(如java jvm和javascript v8),go需要优化的路径还很长(但是相信未来一定是美好的~)。v1.8 混合写屏障(hybrid write barrier)这个版本的 GC 代码相比之前改动还是挺大的,采用一种混合的 write barrier 方式 (Yuasa-style deletion write barrier [Yuasa ‘90] 和 Dijkstra-style insertion write barrier [Dijkstra ‘78])来避免 堆栈重新扫描。混合屏障的优势在于它允许堆栈扫描永久地使堆栈变黑(没有STW并且没有写入堆栈的障碍),这完全消除了堆栈重新扫描的需要,从而消除了对堆栈屏障的需求。重新扫描列表。特别是堆栈障碍在整个运行时引入了显着的复杂性,并且干扰了来自外部工具(如GDB和基于内核的分析器)的堆栈遍历。此外,与Dijkstra风格的写屏障一样,混合屏障不需要读屏障,因此指针读取是常规的内存读取; 它确保了进步,因为物体单调地从白色到灰色再到黑色。混合屏障的缺点很小。它可能会导致更多的浮动垃圾,因为它会在标记阶段的任何时刻保留从根(堆栈除外)可到达的所有内容。然而,在实践中,当前的Dijkstra障碍可能几乎保留不变。混合屏障还禁止某些优化:特别是,如果Go编译器可以静态地显示指针是nil,则Go编译器当前省略写屏障,但是在这种情况下混合屏障需要写屏障。这可能会略微增加二进制大小。小结:通过go team多年对gc的不断改进和忧化,GC的卡顿问题在1.8 版本基本上可以做到 1 毫秒以下的 GC 级别。 实际上,gc低延迟是有代价的,其中最大的是吞吐量的下降。由于需要实现并行处理,线程间同步和多余的数据生成复制都会占用实际逻辑业务代码运行的时间。GHC的全局停止GC对于实现高吞吐量来说是十分合适的,而Go则更擅长与低延迟。 并行GC的第二个代价是不可预测的堆空间扩大。程序在GC的运行期间仍能不断分配任意大小的堆空间,因此我们需要在到达最大的堆空间之前实行一次GC,但是过早实行GC会造成不必要的GC扫描,这也是需要衡量利弊的。因此在使用Go时,需要自行保证程序有足够的内存空间。垃圾收集是一个难题,没有所谓十全十美的方案,通常是为了适应应用场景做出的一种取舍。相信GO未来会更好。参考:https://github.com/golang/pro…http://legendtkl.com/2017/04/...https://blog.twitch.tv/gos-ma...https://blog.plan99.net/moder...links目录 ...

February 15, 2019 · 1 min · jiezi

CMS日志

GC参数配置-XX:+PrintGC输出GC日志。-verbose:gc可以认为 -verbose:gc 是 -XX:+PrintGC 的别名。-Xloggc:log/gc.log输出GC日志的存储路径。-XX:+PrintGCDetails输出GC的详细日志。-XX:+PrintGCDateStamps输出GC的时间戳(以日期的形式)。-XX:+ExplicitGCInvokesConcurrent无论什么时候调用系统GC,都执行CMS GC,而不是Full GC。-XX:+PrintHeapAtGC在进行GC的前后打印出堆的信息。-Xms512m设置初始堆的大小。-Xmx512m堆分配的最大空间。-Xmn1024m设置年轻代大小。整个JVM内存大小 = 年轻代大小 + 年老代大小 + 持久代大小,持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。-Xss320K设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。-XX:+UseConcMarkSweepGC使用CMS收集器,其它类似。-XX:NewRatio=4设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5,默认值是2即老年代是新生代内存的2倍-XX:SurvivorRatio=4设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6,默认值为8。-XX:MaxPermSize=16m设置持久代大小为16m。-XX:MaxTenuringThreshold=0设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。 GC[Allocation Failure]表示向young generation(eden)给新对象申请空间,但是young generation(eden)剩余的合适空间不够所需的大小导致的minor gc。2. Minor GC: UseParNewGC 回收日志2018-12-29T14:39:40.102+0800: 3.856: [GC (Allocation Failure) 2018-12-29T14:39:40.102+0800: 3.856: [ParNew: 681600K->72044K(766784K), 0.0977206 secs] 681600K->72044K(4109120K), 0.0978554 secs] [Times: user=0.39 sys=0.06, real=0.10 secs]2018-12-29T14:39:40.102+0800 本次gc触发的时间3.856 jvm启动后所经历的秒数GC (Allocation Failure) 对象分配在新生代但空间不够导致触发一次新生代gcParNew 表示这是新生代gc类型, 即ParNewGC681600K->72044K(766784K), 0.0977206 secs 触发新生代gc时年轻代堆的大小,回收后的年轻代堆的大小,年轻代堆的总大小,该次gc回收所耗费的时间681600K->72044K(4109120K) 回收前堆的总大小,回收后堆的总大小,堆的总大小3. Major GC: UseConcMarkSweepGC 垃圾收集CMS垃圾收集器总共分为7个阶段,其中有2个结算,即是初始标记和最终标记阶段,是需要暂停用户线程的,其余垃圾收集线程均有用户线程并发执行。3.1 Phase 1: Initial Mark 初始标记2018-12-29T14:46:45.247+0800: 429.000: [GC (CMS Initial Mark) [1 CMS-initial-mark: 17305K(3342336K)] 607785K(4109120K), 0.0704856 secs] [Times: user=0.32 sys=0.10, real=0.07 secs] 这是CMS中两次stop-the-world事件中的一次。它有两个目标:一是标记老年代中所有的GC Roots;二是标记被年轻代中活着的对象引用的对象。[1 CMS-initial-mark 收集阶段,开始收集所有的GC Roots和直接引用到的对象17305K(3342336K) 当前老年代的使用情况,老年代可用容量607785K(4109120K) 当前整个堆的使用情况,整个堆的容量,即是整个堆 - 老年代 = 新生代(4109120 - 3342336 = 766784)3.2 Phase 2: Concurrent Mark 并发标记2018-12-29T14:46:45.317+0800: 429.071: [CMS-concurrent-mark-start]2018-12-29T14:46:45.326+0800: 429.080: [CMS-concurrent-mark: 0.009/0.009 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]这个阶段会遍历整个老年代并且标记所有存活的对象,从“初始化标记”阶段找到的GC Roots开始。并发标记的特点是和应用程序线程同时运行。并不是老年代的所有存活对象都会被标记,因为标记的同时应用程序会改变一些对象的引用等CMS-concurrent-mark 并发收集阶段,这个阶段会遍历整个年老代并且标记活着的对象0.009/0.009 secs 展示该阶段持续的时间和时钟时间3.3 Phase 3: Concurrent Preclean 并发预清除2018-12-29T14:46:45.326+0800: 429.080: [CMS-concurrent-preclean-start]2018-12-29T14:46:45.338+0800: 429.092: [CMS-concurrent-preclean: 0.012/0.012 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]这个阶段又是一个并发阶段,和应用线程并行运行,不会中断他们。前一个阶段在并行运行的时候,一些对象的引用已经发生了变化,当这些引用发生变化的时候,JVM会标记堆的这个区域为Dirty Card(包含被标记但是改变了的对象,被认为"dirty"),这就是 Card Marking。在pre-clean阶段,那些能够从dirty card对象到达的对象也会被标记,这个标记做完之后,dirty card标记就会被清除了一些必要的清扫工作也会做,还会做一些final remark阶段需要的准备工作CMS-concurrent-preclean 这个阶段负责前一个阶段标记了又发生改变的对象标记3.4 Phase 4: Concurrent Abortable Preclean 可终止的并发预清理2018-12-29T14:46:45.338+0800: 429.092: [CMS-concurrent-abortable-preclean-start]CMS: abort preclean due to time 2018-12-29T14:46:50.484+0800: 434.238: [CMS-concurrent-abortable-preclean: 4.232/5.146 secs] [Times: user=5.49 sys=0.10, real=5.15 secs] 又一个并发阶段不会停止应用程序线程。这个阶段尝试着去承担STW的Final Remark阶段足够多的工作。这个阶段持续的时间依赖好多的因素,由于这个阶段是重复的做相同的事情直到发生aboart的条件(比如:重复的次数、多少量的工作、持续的时间等等)之一才会停止CMS-concurrent-abortable-preclean 可终止的并发预清理这个阶段很大程度的影响着即将来临的Final Remark的停顿,有相当一部分重要的 configuration options 和 失败的模式3.5 Phase 5: Final Remark 最终标记2018-12-29T14:46:50.485+0800: 434.239: [GC (CMS Final Remark) [YG occupancy: 632074 K (766784 K)]2018-12-29T14:46:50.485+0800: 434.239: [Rescan (parallel) , 0.0791637 secs]2018-12-29T14:46:50.564+0800: 434.318: [weak refs processing, 0.0001243 secs]2018-12-29T14:46:50.565+0800: 434.318: [class unloading, 0.0409380 secs]2018-12-29T14:46:50.605+0800: 434.359: [scrub symbol table, 0.0136356 secs]2018-12-29T14:46:50.619+0800: 434.373: [scrub string table, 0.0015586 secs][1 CMS-remark: 17305K(3342336K)] 649380K(4109120K), 0.1370772 secs] [Times: user=0.46 sys=0.06, real=0.13 secs] 这个阶段是CMS中第二个并且是最后一个STW的阶段。该阶段的任务是完成标记整个年老代的所有的存活对象。由于之前的预处理是并发的,它可能跟不上应用程序改变的速度,这个时候,STW是非常需要的来完成这个严酷考验的阶段。通常CMS尽量运行Final Remark阶段在年轻代是足够干净的时候,目的是消除紧接着的连续的几个STW阶段CMS Final Remark 收集阶段,这个阶段会标记老年代全部的存活对象,包括那些在并发标记阶段更改的或者新创建的引用对象YG occupancy: 632074 K (766784 K) 年轻代当前占用的情况和容量Rescan (parallel) 这个阶段在应用停止的阶段完成存活对象的标记工作weak refs processing 第一个子阶段,随着这个阶段的进行处理弱引用class unloading 第二个子阶段, 类的卸载scrub symbol table 最后一个子阶段, 清理字符引用等1 CMS-remark: 17305K(3342336K) 在这个阶段之后老年代占有的内存大小和老年代的容量649380K(4109120K) 在这个阶段之后整个堆的内存大小和整个堆的容量通过以上5个阶段的标记,老年代所有存活的对象已经被标记并且现在要通过Garbage Collector采用清扫的方式回收那些不能用的对象了3.6 Phase 6: Concurrent Sweep 并发清除2018-12-29T14:46:50.622+0800: 434.376: [CMS-concurrent-sweep-start]2018-12-29T14:46:50.635+0800: 434.388: [CMS-concurrent-sweep: 0.012/0.012 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 和应用线程同时进行,不需要STW。这个阶段的目的就是移除那些不用的对象,回收他们占用的空间并且为将来使用。CMS-concurrent-sweep 这个阶段主要是清除那些没有标记的对象并且回收空间3.7 Phase 7: Concurrent Reset 并发重置2018-12-29T14:46:50.635+0800: 434.388: [CMS-concurrent-reset-start]2018-12-29T14:46:50.651+0800: 434.405: [CMS-concurrent-reset: 0.016/0.016 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 这个阶段并发执行,重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用CMS-concurrent-reset 这个阶段重新设置CMS算法内部的数据结构,为下一个收集阶段做准备JVM调优——之CMS GC日志分析

January 2, 2019 · 2 min · jiezi