没错,老板让我写个 BUG!
前言
标题没有看错,真的是让我写个 bug
!
刚接到这个需求时我内心没有丝毫波澜,甚至还有点激动。这可是我特长啊;终于可以光明正大的写 bug
了🙄。
先来看看具体是要干啥吧,其实主要就是要让一些负载很低的服务器额外消耗一些内存、CPU 等资源(至于背景就不多说了),让它的负载可以提高一些。
JVM 内存分配回顾
于是我刷刷一把梭的就把代码写好了,大概如下:
写完之后我就在想一个问题,代码中的 mem
对象在方法执行完之后会不会被立即回收呢?我想肯定会有一部分人认为就是在方法执行完之后回收。
我也正儿八经的去调研了下,问了一些朋友;果不其然确实有一部分认为是在方法执行完毕之后回收。
那事实情况如何呢?我做了一个试验。
我用以下的启动参数将刚才这个应用启动起来。
java -Djava.rmi.server.hostname=10.xx.xx.xx -Djava.security.policy=jstatd.all.policy -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=8888 -Xms4g -Xmx4g -jar bug-0.0.1-SNAPSHOT.jar
这样我就可以通过 JMX 端口远程连接到这个应用观察内存、GC 情况了。
如果是方法执行完毕就回收 mem
对象,当我分配 250M
内存时;内存就会有一个明显的曲线,同时 GC 也会执行。
这时观察内存曲线。
会发现确实有明显的涨幅,但是之后并没有立即回收,而是一直保持在这个水位。同时左边的 GC 也没有任何的反应。
用 jstat
查看内存布局也是同样的情况。
不管是 YGC,FGC
都没有,只是 Eden 区的使用占比有所增加,毕竟分配了 250M 内存嘛。
那怎样才会回收呢?
我再次分配了两个 250M 之后观察内存曲线。
发现第三个 250M 的时候 Eden
区达到了 98.83%
于是再次分配时就需要回收 Eden
区产生了 YGC
。
同时内存曲线也得到了下降。
整个的换算过程如图:
由于初始化的堆内存为 4G
,所以算出来的 Eden
区大概为 1092M
内存。
加上应用启动 Spring
之类消耗的大约 20%
内存,所以分配 3 次 250M 内存就会导致 YGC
。
再来回顾下刚才的问题:
mem
对象既然在方法执行完毕后不会回收,那什么时候回收呢。
其实只要记住一点即可:对象都需要垃圾回收器发生 GC
时才能回收;不管这个对象是局部变量还是全局变量。
通过刚才的实验也发现了,当 Eden
区空间不足产生 YGC
时才会回收掉我们创建的 mem
对象。
但这里其实还有一个隐藏条件:那就是这个对象是局部变量。如果该对象是全局变量那依然不能被回收。
也就是我们常说的对象不可达,这样不可达的对象在 GC
发生时就会被认为是需要回收的对象从而进行回收。
在多考虑下,为什么有些人会认为方法执行完毕后局部变量会被回收呢?
我想这应当是记混了,其实方法执行完毕后回收的是栈帧
。
它最直接的结果就是导致 mem
这个对象没有被引用了。但没有引用并不代表会被马上回收,也就是上面说到的需要产生 GC
才会回收。
所以使用的是上面提到的对象不可达所采用的可达性分析算法来表明哪些对象需要被回收。
当对象没有被引用后也就认为不可达了。
这里有一张动图比较清晰:
当方法执行完之后其中的 mem
对象就相当于图中的 Object 5
,所以在 GC
时候就会回收掉。
优先在 Eden 区分配对象
其实从上面的例子中可以看出对象是优先分配在新生代中 Eden 区的,但有个前提就是对象不能太大。
以前也写过相关的内容:
大对象直接进入老年代
而大对象则是直接分配到老年代中(至于多大算大,可以通过参数配置)。
当我直接分配 1000M 内存时,由于 Eden 区不能直接装下,所以改为分配在老年代中。
可以看到 Eden
区几乎没有变动,但是老年代却涨了 37% ,根据之前计算的老年代内存 2730M
算出来也差不多是 1000M
的内存。
Linux 内存查看
回到这次我需要完成的需求:增加服务器内存和 CPU 的消耗。
CPU 还好,本身就有一定的使用,同时每创建一个对象也会消耗一些 CPU。
主要是内存,先来看下没启动这个应用之前的内存情况。
大概只使用了 3G 的内存。
启动应用之后大概只消耗了 600M 左右的内存。
为了满足需求我需要分配一些内存,但这里有点需要讲究。
不能一直分配内存,这样会导致 CPU 负载太高了,同时内存也会由于 GC 回收导致占用也不是特别多。
所以我需要少量的分配,让大多数对象在新生代中,为了不被回收需要保持在百分之八九十。
同时也需要分配一些大对象到老年代中,也要保持老年代的使用在百分之八九十。
这样才能最大限度的利用这 4G 的堆内存。
于是我做了以下操作:
- 先分配一些小对象在新生代中(800M)保持新生代在90%
- 接着又分配了
老年代内 *(100%-已使用的28%);也就是 2730*60%=1638M
让老年代也在 90% 左右。
效果如上。
最主要的是一次 GC
都没有发生这样也就达到了我的目的。
最终内存消耗了 3.5G 左右。
总结
虽说这次的需求是比较奇葩,但想要精确的控制 JVM
的内存分配还是没那么容易。
需要对它的内存布局,回收都要有一定的了解,写这个 Bug 的过程确实也加深了印象,如果对你有所帮助请不要吝啬你的点赞与分享。
你的点赞与分享是对我最大的支持
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Vue slot的用法
之前看官方文档,由于自己理解的偏差,不知道slot是干嘛的,看到小标题,使用Slot分发内容,就以为 是要往下派发内容。然后就没有理解插槽的概念。其实说白了,使用slot就是先圈一块地,将来可能种花种菜,也有可能在这块地上建房子。然而slot可以以一当十,可以插入很多东西。不知明白否? 由于项目经验有限,这篇我就先跟着官网的知识点走,当然会加入自己的部分项目代码。 关于slot是这样说的, 除非子组件模板包含至少一个 <slot> 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。 最初在 <slot> 标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。 单个 Slot 在子组件内使用特殊的<slot>元素就可以为这个子组件添加一个 slot (插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的<slot>标签及它的内容.示例代码...
- 下一篇
微服务架构设计基础之立方体模型
背景 对于现在的微服务架构的应用来说,对大量并发的及时响应是一项制胜能力。据用户行为分析平台统计,随行付的某一款APP产品每日请求就达到上千万次用户请求、加解密服务3000万次/日等等。这些微服务每时每刻在处理如此高强度的请求,对数据层的应对能力要求极高。如果我们把对速度的需求放在复杂的分布式数据架构背景下,是很难想象如何让应用应对如此巨大的数据访问量的。但很幸运,我们有方法做到。即立方体模型。 立方体模型 可扩展的分布式系统架构设计有一个朴素的理念,就是:通过加机器就可以解决容量和可用性的问题。 对于一个迅速增长的应用而言,容量和性能是首当其冲要面临的问题。但随着时间的向前推移、应用规模不断的快速增长,除了面对性能与容量的问题外,还需要解决功能与模块数量上增长带来的系统复杂性问题、业务变化带来的差异化服务问题等。而多数情况下应用设计之初出于诸多因素的考量,并没有充分考虑或在设计之初就将此类问题提上日程,导致系统的重构成为常态,从而影响业务交付能力。对此,「架构即未来」一书中提出了更加系统的可扩展模型,可扩展模型是一个富有启发性的方法,描述了微服务三个维度的扩展方法,可以通过它来了解微...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程