标题没有看错,真的是让我写个 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 的进程确实也加深了印象,假设对你有所协助请不要吝啬你的点赞与分享。
【编辑引荐】
自在软件之父 RMS:Linux 贡献者无权撤销代码
2018 JVM 生态报告:79% 的 Java 开发者运用 Java 8
微软开放6万项Linux专利背后,我们该留意什么?
顺序员的革命or灾难:机器人在GitHub修复bug、与人不相上下(附论文)
微软发布 Linux 版的 Sysinternals 工具 ProcDump
(责任编辑:admin)