Java的GC机制
回收的对象:不存在任何引用的对象
堆区(Heap)
堆区是GC最频繁的,也是理解GC机制最重要的区域。堆区由所有线程共享,在虚拟机启动时创建。堆区主要用于存放对象实例及数组,所有new出来的对象都存储在该区域。
如何判断对象是垃圾 ?
引用计数算法
经典的引用计数算法,每个对象添加到引用计数器,每被引用一次,计数器+1,失去引用,计数器-1,当计数器在一段时间内为0时,即认为该对象可以被回收了。但是这个算法有个明显的缺陷:当两个对象相互引用,但是二者都已经没有作用时,理应把它们都回收,但是由于它们相互引用,不符合垃圾回收的条件,所以就导致无法处理掉这一块内存区域。因此,Sun的JVM并没有采用这种算法,而是采用一个叫——根搜索算法,如图:
根搜索算法
基本思想是:从一个叫GC Roots的根节点出发,向下搜索,如果一个对象不能达到GC Roots的时候,说明该对象不再被引用,可以被回收。如上图中的Object5、Object6、Object7,虽然它们三个依然相互引用,但是它们其实已经没有作用了,这样就解决了引用计数算法的缺陷。
内存分代
JVM区域总体分两类,heap区和非heap区。
heap区又分为:
Eden Space
(伊甸园)、
对象被创建的时候首先放到这个区域,进行垃圾回收后,不能被回收的对象被放入到空的survivor区域
Survivor Space
(幸存者区)、
用于保存在eden space内存区域中经过垃圾回收后没有被回收的对象
回收的时候Eden区域不能被回收的对象被放入到空的survivor(也就是To Survivor,同时Eden区域的内存会在垃圾回收的过程中全部释放),
另一个survivor(即From Survivor)里不能被回收的对象也会被放入这个survivor(即To Survivor),然后To Survivor 和 From Survivor的标记会互换
,始终保证一个survivor是空的
,涉及到一个算法
Eden Space
和Survivor Space
都属于新生代,新生代中执行的垃圾回收被称之为Minor GC(因为是对新生代进行垃圾回收,所以又被称为Young GC),每一次Young GC后留下来的对象age加1
新生代进行垃圾回收时会出发
Minor GC
(也称作Young GC
)
Old Gen
(老年代)。
非heap区又分:
Code Cache
(代码缓存区);
Perm Gen
(永久代);
(Perm Gen全称是Permanent Generation space,是指内存的永久保存区域)
Jvm Stack
(java虚拟机栈);
默认大小为物理内存的1/64。
不会被JVM垃圾回收
老年代用于存放新生代多次回收依然存活的对象,如缓存对象。老年代满了的时候就需要对老年代进行回收,老年代的垃圾回收称作
Major GC
(也称作Full GC
)
永久代是有大小限制的,因此如果加载的类太多,很有可能导致永久代内存溢出
Local Method Statck
(本地方法栈);
垃圾回收器
1.串行收集
2.并行收集
3.CMS收集器
4.G1收集器
使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。
GC常见算法?
1.复制算法
2.标记--清除算法
3.标记--压缩算法
4.分代回收算法
JVM参数调优
注:jdk8 开始,用 MetaSpace (元空间)区取代了 Perm 区(永久代),所以相应的 jvm 参数变成 -XX:MetaspaceSize 及 -XX:MaxMetaspaceSize。
MetaSpace GC
如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器。根据JDK 8的特性,G1和CMS都会很好地收集Metaspace区(一般都伴随着Full GC)。
为了减少垃圾回收的频率及时间,控制吞吐量,对Metaspace进行适当的监控和调优是非常有必要的。如果在Metaspace区发生了频繁的Full GC,那么可能表示存在内存泄露或Metaspace区的空间太小了。
新增的 JVM 参数
-XX:MetaspaceSize 是分配给类元数据空间(以字节计)的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark ),此值为估计值。MetaspaceSize的值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。
-XX:MaxMetaspaceSize 是分配给类元数据空间的最大值,超过此值就会触发Full GC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。
-XX:MinMetaspaceFreeRatio 表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最小比例,不够就会导致垃圾回收。
-XX:MaxMetaspaceFreeRatio 表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最大比例,不够就会导致垃圾回收。
JVM参数优化
调优原则
调优方法以及原则一切都是为了这一步,调优,在调优之前,我们需要记住下面的原则:
1、多数的Java应用不需要在服务器上进行GC优化;
2、多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;
3、在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合);
4、减少创建对象的数量;
5、减少使用全局变量和大对象;
6、GC优化是到最后不得已才采用的手段;
7、在实际使用中,分析GC情况优化代码比优化GC参数要多得多;
Java虚拟机参数设置:
(1)性能参数:
-server
以server模式运行时将拥有:更大、更高的并发处理能力,更快更强捷的JVM垃圾回收机制,可以获得更多的负载与吞吐量
-Xmx
指定java程序的最大堆内存, 使用java -Xmx5000M -version判断当前系统能分配的最大堆内存
-Xms
指定最小堆内存, 通常设置成跟最大堆内存一样,减少GC
-Xmn
设置年轻代大小为512m。整个堆大小=年轻代大小 + 年老代大小。所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss
指定线程的最大栈空间, 此参数决定了java函数调用的深度, 值越大调用深度越深, 若值太小则容易出栈溢出错误(StackOverflowError)
设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程 大约需要占用多少内存,可能会有多少线程同时运行等。一般不易设置超过1M,要不然容易出现out ofmemory
-XX:PermSize
指定方法区(永久区)的初始值,默认是物理内存的1/64, 在Java8永久区移除, 代之的是元数据区, 由-XX:MetaspaceSize指定
-XX:MaxPermSize
指定方法区的最大值, 默认是物理内存的1/4, 在java8中由-XX:MaxMetaspaceSize指定元数据区的大小
-XX:NewRatio=n
年老代与年轻代的比值,-XX:NewRatio=2, 表示年老代与年轻代的比值为2:1
-XX:SurvivorRatio=n
Eden区与Survivor区的大小比值,-XX:SurvivorRatio=8表示Eden区与Survivor区的大小比值是8:1:1,因为Survivor区有两个(from, to)
-XX:+DoEscapeAnalysis
开启逃逸分析, 逃逸分析的目的是判断对象的作用域是否可能逃逸出函数体, 逃逸分析是栈上分配的技术基础,对于非逃逸对象而言就是一个局部变量, 而对象未发生逃逸时, 虚拟机就有可能进行线上分配, 不是堆上, 栈上分配速度快,并且能避免垃圾回收带来的负面影响, 栈上分配是虚拟机提供的很好的对象分配优化策略
-XX:+EliminateAllocations
开启标量替换(默认打开), 即允许对象打散分配在栈上, 即对象的属性视为独立局部变量进行分配到栈上
-XX:+UseCompressedOops
开启指针压缩
-XX:+AggressiveOpts
启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等
-XX:-UseTLAB
关闭TLAB , 默认是打开的
-Djava.awt.headless=true
有时我们会在我们的J2EE工程中使用一些图表工具如:jfreechart,用于在web网页输出GIF/JPG等流,在winodws环境下,一般我 们的app server在输出图形时不会碰到什么问题,但是在linux/unix环境下经常会碰到一个exception导致你在winodws开发环境下图片显 示的好好可是在linux/unix下却显示不出来,因此加上这个参数以免避这样的情况出现.
-XX:+DisableExplicitGC
在程序代码中不允许有显示的调用”System.gc()”。看到过有两个极品工程中每次在DAO操作结束时手动调用System.gc()一下,觉得这 样做好像能够解决它们的out ofmemory问题一样,付出的代价就是系统响应时间严重降低,就和我在关于Xms,Xmx里的解释的原理一样,这样去调用GC导致系统的JVM大起大 落,性能不到什么地方去哟!
-XX:+UseBiasedLocking
偏向锁,启用一个优化了的线程锁,我们知道在我们的appserver,每个http请求就是一个线程,有的请求短有的请求长,就会有请求排队的现象,甚至还会出现线程阻塞,这个优化了的线程锁使得你的appserver内对线程处理自动进行最优调配。
GC策略
策略 1:将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代是明智的做法,实际项目中根据 GC 日志分析新生代空间大小分配是否合理,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况。
策略 2:大对象进入老年代,虽然大部分情况下,将对象分配在新生代是合理的。但是对于大对象这种做法却值得商榷,大对象如果首次在新生代分配可能会出现空间不足导致很多年龄不够的小对象被分配的老年代,破坏新生代的对象结构,可能会出现频繁的 full gc。因此,对于大对象,可以设置直接进入老年代(当然短命的大对象对于垃圾回收老说简直就是噩梦)。-XX:PretenureSizeThreshold 可以设置直接进入老年代的对象大小。
策略 3:合理设置进入老年代对象的年龄,-XX:MaxTenuringThreshold 设置对象进入老年代的年龄大小,减少老年代的内存占用,降低 full gc 发生的频率。
策略 4:设置稳定的堆大小,堆大小设置有两个参数:-Xms 初始化堆大小,-Xmx 最大堆大小。
策略5:注意:如果满足下面的指标,则一般不需要进行 GC 优化:
MinorGC 执行时间不到50ms;
Minor GC 执行不频繁,约10秒一次;
Full GC 执行时间不到1s;
Full GC 执行频率不算频繁,不低于10分钟1次。
监控JVM
一,需求:大数据的机器,很多jvm进程,需要监控特定的几个的进程的FGC,FGCT,YGC,YGCT,GCT。
YGC:从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
二 编写自定义监控项脚步
jps命令可以获取到进程的ID,jstat -gc ID ,获取gc的信息
参数详解
jstat参考
cat jps1.py
#!/usr/bin/env python
import os,sys
jps_info = os.popen("/usr/bin/sudo /usr/local/java/bin/jps | grep %s | awk '{print $1}'"% sys.argv[1])
jps_info = jps_info.read()
jps_id = jps_info.strip()
jstat = {'YGC':'$13','YGCT':'$14','FGC':'$15','FGCT':'$16','GCT':'$17'}
info = jstat[sys.argv[2]]
command = "/usr/bin/sudo /usr/local/java/bin/jstat -gc "+jps_id+" | awk '{print "+info+"}'| /usr/bin/tail -n 1"
os.system(command)
三,编辑zabbix agent配置文件
添加:UserParameter=jstat[*], /usr/bin/python /etc/zabbix/scripts/jps1.py $1 $2
四,其它地方的调整
在本机测试脚本是正常的,但是在zabbix server上测试有几个问题需要修改
编辑 /etc/sudoers文件,添加一行:
zabbix ALL=(ALL) NOPASSWD:ALL
这一行需要注释掉:#Defaults requiretty
五,创建监控项(数据类型选择浮点数)
六,添加模板展示
如何修改JVM参数
在安装目录的bin/catalina.sh 文件的cygwin=false上面加上:
JAVA_OPTS="-Xms256m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=512m"
或者
CATALINA_OPTS="-Xms256m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=512m"
参考:
https://www.sczyh30.com/posts/Java/jvm-metaspace/
https://www.jianshu.com/p/bc2e4d4ff018