调优最主要的调优方式:
堆大小设置。
回收器选择。
1、内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。
2、对JVM内存的系统级的调优主要的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理,导致Full GC一般由于以下几种情况:
1、旧生代空间不足
调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象
2、Pemanet Generation空间不足
增大Perm Gen空间,避免太多静态对象
统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间
控制好新生代和旧生代的比例
3、System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制
JAVA内存结构
图片.png
内存模型分为5部分
jvm.png
堆内存:存储new出来的对象,静态对象也会将对象存储在这, 堆内存的对象分为新生代和老年代
方法区: 1.8之后叫做元空间,永久代实现, 存储的是
1.类的字节码
2.static修饰的变量
3.static修饰的方法
4.常量
程序计数器: 计算java代码运行到的位置
虚拟机线程栈: 引用变量,基本数据类型
本地方法栈: native修饰的方法,在该区域分配内存,一般native都是调用其他技术框架
分代收集算法:
jvm.png
如果伊甸园区的内存满了的话会去执行minor gc对无效对象做回收.
什么是无效对象呢?
就是在方法区没有引用指向堆内存中的对象,但是不排除还有一些有引用的对象,此时就会将他们放入survivor区中,
此时会出现一个分代年龄,如果经历过一次minor gc的话分代年龄会被加一
survivor区满了之后也会触发minor gc对无效对象做回收,还有一些有引用的对象将他们放入survivor1区中
如果survivor1也满了之后会再次放到survivor区,这里会有一个复制算法,就是将survivor1中的无效对象清理完成之后全部移到survivor区中,这里会轮询很多次,每次做一次minorgc回收就会对分代年龄+1
等分代年龄到达十五之后,jvm就会将那些有引用的对象放到老年代中(比如静态变量).
当老年代满了也会进行gc full gc,程序抛异常,内存溢出oom.
图片.png
如何判断一个对象需要被gc回收呢?
看对象还有没有被引用(引用计数器)
原理:当有一个人引用它计数器加一,当计数器为0就代表需要回收,对象已死了.
它有一个缺点,就是引用互相引用,造成循环引用的问题,引用计数无法解决此问题
java采用可达性分析法
可达性分析算法:
将Gc roots的对象作为起点,从这些几点开始向下搜索,走过的路劲称为引用链,当Roots没有任何引用链相连的时候(从gc roots到这个对象不可达),代表不可用.
jvm.png
怎么将垃圾进行回收呢?
标记清除算法:
问题:碎片化,可用空间分散
2.标记整理算法:
标记整理算法没有标记清除算法效率高,因为要将有引用的进行分界
jvm.png
先让存活的对象向一端移动,然后直接清除边界以外的内存
3.复制算法:
每次会先将内存分为俩块左边和右边俩块,要进行gc时就只需要找到左边的内存,找出可用的对象,吧可用的对象转到另一部分,将第一部分的内存直接gc掉,一直来回此做法,可解决对象碎片化
jvm.png
垃圾回收器
jvm.png
- 新生代回收器:Serial、ParNew、Parallel Scavenge
- 老年代回收器:Serial Old、Parallel Old、CMS
- 整堆回收器:G1
用线牵连的可以搭配使用
流行的商用垃圾收集器:分代回收
分代垃圾回收器把内存分为:新生代(Young Generation)和老生代(Tenured Generation)
新生代垃圾回收的执行过程:
1、Eden 区 + From Survivor 区存活着的对象复制到 To Survivor 区;
2、清空 Eden 和 From Survivor 分区;
3、From Survivor 和 To Survivor 分区交换(From 变 To,To 变 From)。
老生代(Tenured Generation)
老生代垃圾回收的频率比新生代低,存放的主要对象是:
1、新生代对象经过 N 次 GC 晋升到老年代。
可以通过设置 -XX:MaxTenuringThreshold=5 来设置,默认值是 15 次。
2、大对象直接存储到老生代。
所谓的“大对象”指的是需要连续存储空间的对象,比如:数组。
当大对象在新生代存储不下的时候,就需要分配担保机制,把当前新生代的所有对象复制到老年代中,因为分配担保机制需要涉及大量的复制,会导致性能问题,所有最好的方案是直接把大对象存储到老生代中。
通过参数 -xx:PretrnureSizeThreshold 来设定大对象的值。
注意:该参数只有 Serial 和 ParNew 垃圾回收器有效。
Serial
Serial 最早的垃圾回收器,JDK 1.3.1 之前新生代唯一的垃圾回收器,使用的是单线程串行回收方式,在单 CPU 环境下性能较好,因为单线程执行不存在线程切换。
线程类型: 单线程
使用算法: 复制算法
指定收集器: -XX:+UseSerialGC
Serial Old
Serial 收集器的老年代版本,同样也是单线程的。它有一个实用的用途作为CMS收集器的备选预案,后面介绍CMS的时候会详细介绍。
线程类型: 单线程
使用算法: 标记-整理
指定收集器: -XX:+UseSerialGC
ParNew
ParNew 其实就是 Serial 的多线程版本,可以和 Serial 共用很多控制参数,比如:-XX:SurvivorRatio , ParNew 可以和 CMS 配合使用。
图片.png线程类型: 多线 程
使用算法: 复制
指定收集器: -XX:+UseParNewGC
Parallel Scavenge
Parallel 和 ParNew 收集器类似,也是多线程的,但 Parallel 是吞吐量优先的收集器,GC停顿时间的缩短是以吞吐量为代价的,比如收集 100MB 的内存,需要 10S 的时间,CMS 则会缩短为 7S 收集 50 MB 的内存,这样停顿的时间确实缩少了,但收集的频率变大了,吞吐量就变小了。
线程类型: 多线程
使用算法: 复制
指定收集器: -XX:+UseParallelGC
Parallel Old
Parallel Old 是 Parallel 的老生代版本,同样是吞吐量优先的收集器。
线程类型: 多线程
使用算法: 标记-整理
指定收集器: -XX:+UseParallelOldGC
CMS
CMS(Concurrent Mark Sweep)一种以获得最短停顿时间为目标的收集器,非常适用B/S系统。
使用 Serial Old 整理内存。
CMS 运行过程:
图片.png
1、初始标记
标记 GC Roots 直接关联的对象,需要 Stop The World 。
2、并发标记
从 GC Roots 开始对堆进行可达性分析,找出活对象。
3、重新标记
重新标记阶段为了修正并发期间由于用户进行运作导致的标记变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,也需要 Stop The World 。
4、并发清除
除垃圾对象。
CMS 缺点:
1、对 CPU 资源要求敏感。
CMS 回收器过分依赖于多线程环境,默认情况下,开启的线程数为(CPU 的数量 + 3)/ 4,当 CPU 数量少于 4 个时,CMS 对用户本身的操作的影响将会很大,因为要分出一半的运算能力去执行回收器线程。
2、CMS无法清除浮动垃圾。
浮动垃圾指的是CMS清除垃圾的时候,还有用户线程产生新的垃圾,这部分未被标记的垃圾叫做“浮动垃圾”,只能在下次 GC 的时候进行清除。
3、CMS 垃圾回收会产生大量空间碎片。
CMS 使用的是标记-清除算法,所有在垃圾回收的时候回产生大量的空间碎片。
注意:CMS 收集器中,当老生代中的内存使用超过一定的比例时,系统将会进行垃圾回收;当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时采用 Serial Old 算法进行清除,此时的性能将会降低。
线程类型: 多线程
使用算法: 标记-清除
指定收集器: -XX:+UseConcMarkSweepGC
G1
G1 GC 这是一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。G1 可以直观的设定停顿时间的目标,相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。
G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候,G1 的优势更加明显。
图片.png
G1 吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时 CMS 已经在 JDK 9 中被标记为废弃(deprecated),所以 G1 GC 值得深入掌握。
G1 运行过程:
1、初始标记
标记 GC Roots 直接关联的对象,需要 Stop The World 。
2、并发标记
从 GC Roots 开始对堆进行可达性分析,找出活对象。
3、重新标记
重新标记阶段为了修正并发期间由于用户进行运作导致的标记变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,也需要 Stop The World 。
4、筛选回收
首先对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。这个阶段可以与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的。
线程类型: 多线程
使用算法: 复制、标记-整理
指定收集器: -XX:+UseG1GC(JDK 7u4 版本后可用)