版本:JDK8
一、阅读前热身:
1、了解jvm启动流程:
2、了解硬件、系统、进程三个层面的内存之间的概要内存分配,一张图你就懂:
3、下面是需要背住的重点,敲黑板!!堆内存分配,想了解参数的可以到最下面看下备注和建议:
先来个日志(看不懂那就看图):
备注:
Heap(堆内存)=eden+2survivor(年轻代)+ParOldGen(老生代)+Perm(jdk8以前)。
jdk8以后将永久代替换为MetaSpace(元空间)存在于本地内存。
from survivor 和 to survivor大小相同,且保证一个为empty.
二、实战演练
1、JVM GC执行日志:
gc解析日志:我们可以看到 由于年轻代的eden区100%,触发了红色框说由于内存分配失败,所以gc回收,eden区gc后剩余60567k(看年轻代蓝色变化)存放于年轻代的survivor(看绿色survivor增长69%)。
红色框解读:
[PSYoungGen: 524800K->60567K(611840K)] 。格式为[PSYoungGen: a->b©].
PSYoungGen,表示新生代使用的是多线程垃圾收集器Parallel Scavenge。a为GC前新生代已占用空间,b为GC后新生代已占用空间。新生代又细分为一个Eden区和两个Survivor区,Minor GC之后Eden区为空,b就是Survivor中已被占用的空间。括号里的c表示整个年轻代的大小。
524800K->60647K(2010112K),格式为x->y(z)。x表示GC前堆的已占用空间,y表示GC后堆已占用空间,z表示堆的总大小。
由新生代和Java堆占用大小可以算出年老代占用空间,此例中就是2010112K-611840K=1398272k。
[Times: user=0.46 sys=0.02, real=0.13 secs] 提供cpu使用及时间消耗,user是用户态消耗的cpu时间,sys是系统态消耗的cpu时间,real是实际的消耗时间。
2、JVM gc执行过程:
执行过程如上图根据执行日志顺序,
第一步新生代内存分配区域eden空间100%后,无法分配内存。
第二步gc回收、保留一部分剩余对象存放survivor,利用copy算法始终保持一个为empty。并为对象age+1.
之间一直循环上述步骤,当age满足很大的时候触发老年代gc回收保存到old 老年代区。
第四步由于不断会有对象进入老年代,老年代内存会一直增大加上新生代达到临界值内存最大值。触发full gc进行整体回收。
疑问点:那你没有说明元空间啊?元空间干啥的呢?
元空间存放:class文件、静态对象、属性等。而且在永久代的时候默认大小256m。但是在元空间jdk8后,它是jvm根据需要动态加载大小。
三、实战后记忆:
网上一个很形象的例子描述对象在JVM堆内存中的生命周期:
我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“To”区,自从去了Survivor区,我就开始漂泊了,因为Survivor的两个区总是交换名字,所以我总是搬家,搬到To Survivor居住,搬来搬去,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
四、性能调优建议:
jvm调优没有一个固定模板配置说必须如何操作,它需要根据系统的情况不同对待。
但是可以有如下建议:
1、初始化内存和最大内存尽量保持一致,避免内存不够用继续扩充内存。最大内存不要超过物理内存,例如内存8g,你可以设置最大内存4g/6g但是不能超过8g否则加载类的时候没有空间会报错。
2、gc/full gc频率不要太高、每次gc时间不要太长、根据系统应用来定。
五、算法描述:
引用计数(Reference Counting):
比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
标记-清除(Mark-Sweep):
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。
标记-整理(Mark-Compact):
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
六、本篇文章不足点:
此篇文章参数只设置了初始化内存和最大内存其余都是系统自动分配,接下来会不断进行优化配置,面试掌握上面的内容基本上就可以了。
如果需要深入了解想要加入BATJ/TMD的,那么还需要点击下面深入了解下。
2019-03-06 15:57:00更新
深入了解JVM,可以点击这里