JAVA GC垃圾回收调优初尝试
常见的JVM命令:
//最小堆内存
-Xms1024m
//最大堆内存
-Xmx2048m
//内存溢出时进行堆转储
-XX:+HeapDumpOnOutOfMemoryError
//转储文件路径
-XX:HeapDumpPath=/log/sfgk.hprof
//垃圾回收器进行垃圾回收时候打印垃圾回收日志
-XX:+PrintGCDetails
常见垃圾回收器:
年轻代垃圾回收器:
Serial、ParNew、Parallel Scavenge
老年代垃圾回收器:
CMS、Serial Old、Parallel Old
整体垃圾回收器:
G1
不了解的垃圾回收器 :
ZGC
JDK8默认垃圾回收器:
年轻代+老年代:Parallel Scavenge、Parallel Old
概念:
什么是垃圾?
其他无垃圾回收器的语言,都是程序员new了对象占用了内存以后,对象占用了空间,在对象用完之后,都需要销毁对象,释放内存,而Java不存在手动释放对象的内存就是因为JVM自动的释放内存,依赖于JVM的内存回收机制。垃圾就是在内存中存在的,使用过,无用的一些对象,因为他们占用了内存,所以咱们通常认为它们就是内存里的垃圾。
怎么定位内存里的垃圾?
引用计数法
概念:
引用和对象有关联,操作对象的时候,必须要使用它的引用,所以判断它是否是个垃圾对象,最简单的办法就是确定这个对象还有人引用它么?
可达性分析
概念:引用计数法并不完善,如果有3个对象,A引用B,B引用C,C引用A,那么这3个对象之间永远都有引用关系,所以出现了可达性分析这种方法,从“GC roots”触发如果它和某一个对象之间是不可达的,才认为有可能是垃圾。
图中的圆圈都是对象,大家描述一下,都哪些对象有可能被JVM识别成垃圾呢?
回收算法
上面我们讲了怎么查找垃圾,下面我们来讲一讲垃圾是如何回收的。
1.标记清除
顾名思义,就是先定位垃圾,再直接垃圾,释放对象空间的方式来释放内存。
缺点:
①内存空间不连续。
②大对象可能存不下。
联想:
①可以想象键盘上有几个键帽脏了,拔掉了这几个脏的键帽,导致键盘看起来很不舒服,空间不连续。
②new对象的时候也可能会出现某一块空间太小,整体空闲的内存够,而因为不连续的空间导致,无法储存某个对象的情况。
2.复制算法
复制算法就是先将内存分成两个区域A、B。
new对象的时候先使用A区的内存,当内存回收时,将不是垃圾的对象统一的存储B区,这样的话不会标记清除方法中出现的内存不连续的问题。
缺点:
内存空间只能使用一半,另一半在空闲。
联想:
有A、B两块空闲的田地,A地上种树,B空闲,种着种着发现里面有某一些树生病了,需要隔离其他的没有生病的树,就把A区域里健康的树转移到B区去,然后再统一的将A区清空。
3.标记整理算法
标记整理算法就是将内存里面有用的对象移动到内存的一端,然后清除边界以外的全部对象。标记整理算法解决了标记清除和复制算法的缺点。
联想:
有一大堆食物。有爱吃的不爱吃的,垃圾回收器根据他的标准(是否是垃圾),把好吃的推到一边了,其他的区域要么就空闲的,要么是垃圾,全部清理掉就可以了。
JAVA分代回收
分代回收垃圾是现在大部分JVM所采用的方法,核心思想是划分几块内存区域,一般情况下划分为新生代、老年代
新生代特点
每次回收都能淘汰大量的对象,释放很大的空间。
老年代特点
一些对象长期被识别为非垃圾。少量的对象被发现回收。
内存区域(分代回收模型)
内存区域(Memery)分为新生代(Young+S1+S2)、老年代
分代回收和回收算法之间的关系
新生代回收 MinorGC
刚new出来的小对象被存放在Young区,当一次回收没有回收掉的情况下,就会使用复制算法将标识为垃圾的对象转存到S1或者S2区。当Young区发生回收时,S1区剩余存活的对象,会被迁移到S2区。当对象在S区回收到一定次数之后,还没有被回收掉,那么就认为这个对象是老年区对象。会被转移到Old区。
S1和S2区并没有区别,可以理解为人人都是S1区,人人都是S2区
老年代回收 MajorGC
老年代区的对象主要是一些大的对象,或者是经历过多次回收却没有被回收的对象。MajorGc采用标记清除算法先扫描再回收,会消耗很多时间,会产生内存碎片,为了减少内存的损耗,一般进行合并或者标记出来方便下次直接分配。老年代如果装不下了,就会抛出OOM错误了。