文章来源:https:///a2572371/article/details/78202874
一.根据Java虚拟机规范,JVM将内存划分为:
1.New(年轻代)
2.Tenured(年老代)
3.永久代(Perm)(备注:jdk1.8之后 改为元本地元空间)
其中,New和Tenured属于堆内存。-xmx:指定jvm堆内存最大值,-xms:jvm堆初始化值。
永久代,Perm(非堆)不属于堆内存,有虚拟机直接分配,但可以通过-XX:PermSize -XX:MaxPermSize等参数调整其大小。
年轻代(New):年轻代用来存放JVM刚分配的Java对象
年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代
永久代(Perm):永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。
New又分为几个部分:
Eden:用来存放JVM刚分配的对象
Survivor1
Survivro2:两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回Copy,当满足某个条件,比如Copy次数,就会被Copy到 Tenured。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。
二.垃圾回收算法
垃圾回收算法可以分为三类,都基于标记-清除(复制)算法:
1.Serial算法(单线程)
2.并行算法
3.并发算法
JVM会根据机器的硬件配置对每个内存代选择适合的回收算法,比如,如果机器多于1个核,会对年轻代选择并行算法。
并行算法是用多线程进行垃圾回收,回收期间会暂停程序的执行,而并发算法,也是多线程回收,但期间不停止应用执行
并发算法适用于交互性高的一些程序。经过观察,并发算法会减少年轻代的大小,其实就是使用了一个大的年老代,这反过来跟并行算法相比吞吐量相对较低
调优:
(1)针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相 同的值
(2)年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小
(3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响
更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
jvm设置
java -Xmx355m -Xms355m -Xmn200m -Xss128k
-Xmx3550m:设置JVM最大内存
-Xms3550m:设置JVM初始内存。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对
系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减
小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将
此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
Minor GC ,Full GC 触发条件
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法去空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
重点:
1. jvm调优思路
jvm调优其实更多的是对GC的优化,尤其是尽量减少full GC。
大多数情况下,对象在Eden区分配,当Eden区没有足够空间进行分配时,虚拟机将进行一次Minor GC ,可能有99%的对象被标记为垃圾被回收,剩余存活的对象会进入为空的survivor,下一次Eden区满了之后,又会触发minor gc,把Eden区和survivor区垃圾对象回收,把剩余存活的对象一次性挪动到另外一块为空的to区,因为新生代的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适的,让eden区尽量的大,survivor区够用即可。
Minor GC/Young GC:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
Major GC/Full GC:一般会回收老年代 ,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC的慢 10倍以上。
Eden与Survivor区默认8:1:1
明白了上边的对象流转过程,我们可以在这个过程中做一些手脚,来进行jvm调优!
2 jvm调优方案
①:设置大对象直接进入老年代! 大对象就是需要大量内存空间的对象(比如数组、字符串) ,通过jvm参数-XX:PretenureSizeThreshold 可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下有效。
具体操作:-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC
使用场景:当我们可以确定系统中的对象大部分为大对象,且短期内不会被垃圾回收,就可以根据对象大小设置jvm参数,让这些大对象直接进入老年代,省去了对象在新生代流转的过程,节省了Eden区的空间,因为大对象最终总会进入老年代的,还不如提前让出Eden空间,让他处理更多的小对象,提升系统性能!
②:设置长期存活的对象提前进入老年代! 新生代的对象每熬过一次Minor GC ,其年龄就会+1,默认15岁,也就是流转15次就会进入老年代。当我们的系统中大概有大部分(80%)的对象都会经过15次Minor GC 进入老年代,我们可以通过设置-XX:MaxTenuringThreshold来调整进入老年代需要的年龄阈值。比如设置年龄为8即可进入老年代,这样那些长期存活的对象,就可以尽早的进入老年代,减少对象在新生代的流转次数,提升了系统性能!
③:根据survivor区的动态年龄判断机制,合理设置新生代大小 。一般超过survivor区大小的60%会发生动态年龄判断机制,此时把最老的对象放进老年代。可以适当增加survivor区的大小避免Full GC!动态年龄判断的机制作用其实是希望那些可能是长期存活的对象,尽早进入老年代,避免多次复制操作而降低效率。
其他情况调优:( Java heap space)
针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理:
1、如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制。
2、如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。
3、如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接。
PermGen space
该错误表示永久代 (Permanent Generation) 已用满, 通常是因为加载的 class 数目太多或体积太大
根据 Permgen space 报错的时机,可以采用不同的解决方案,如下所示:
1、程序启动报错,修改 -XX:MaxPermSize 启动参数,调大永久代空间。
2、应用重新部署时报错,很可能是没有应用没有重启,导致加载了多份 class 信息,只需重启 JVM 即可解决。
3、运行时报错,应用程序可能会动态创建大量 class,而这些 class 的生命周期很短暂,但是 JVM 默认不会卸载 class,可以设置 -XX:+CMSClassUnloadingEnabled 和 -XX:+UseConcMarkSweepGC这两个参数允许 JVM 卸载 class。
4、如果上述方法无法解决,可以通过 jmap 命令 dump 内存对象 jmap-dump:format=b,file=dump.hprof ,然后利用 Eclipse MAT https://www.eclipse.org/mat 功能逐一分析开销最大的 classloader 和重复 class。
订单的秒杀模块jvm调优案例
架构如下:
对亿级电商平台的调优中,首先要对自己的系统有足够的了解。根据以上架构:
①:如果平台日活用户为500w,那大部分的付费转化率为10%,也就是每日50w单左右。
②:如果50w单是在平时非促销的时候,下订单操作通过负载均衡打到服务器上,也就每秒几单、十几单的样子,服务器可以抗住。但如果要搞促销,50w单要在几分钟内产生,那么每秒钟就高达1000多单的交易,如果不合理设置jvm参数,就可能会频繁发生Full GC!
③:假设有订单三台服务器,每秒1000单通过负载均衡到三台服务器,每台服务器每秒处理300单左右!每个订单对象的大小是由order类中的字段来决定的,假设每个订单对象有几十个字段,撑死1kb,每秒300kb对象生成,订单接口中肯定不止一个订单对象,还有库存、优惠券、积分等对象,所以300kb放大20倍,也就是6MB,同时还可能有别的操作,比如订单查询等,再放大10倍,也就是60MB,因为要保证请求速度,所以这60MB对象1s后都会成为垃圾。
④:此时每秒60M对象进入堆内存。假如服务器内存是8G,给操作系统分配4-5G,剩下的分给JVM,因为 新生代:老年代 = 1:2,则新生代拿到1G,老年代2G,元空间512Mb,栈1M,jvm参数设置 如图所示!对象会首先进入Eden,800/60 ≈ 14秒 ,也即14秒时Eden区被放满,发生Minor GC!
⑤:Minor GC会进行stw,前13秒的对象直接被gc掉。然而由于gc时的stw,用户线程无法执行,此时可能第14秒创建的对象会没有使用完,就会一直被引用着,GC完毕后,第14秒创建的对象由于被引用而存活了下来,此时存活的对象会进入survivor区。由于survivor区存在动态年龄判断机制:如果Survivor区中的这批对象总大小大于Survivor区域内存大小的50%,那么>=这批年龄最大值的对象,都会被放入老年代。 该组对象大小>50MB,年龄都是1,所以此时第14秒这批对象都会直接进入老年代。也就是每14秒60M对象进入老年代!导致几分钟一次GC!
⑥:由于上述原因就是由survivor区存在动态年龄判断机制导致的,所以我们可以通过调整新生代大小来避免,对象进入老年代!
JVM工具监控和分析
使用各种JVM工具,查看当前日志,分析当前JVM参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分和GC执行时间,觉得是否进行优化。
举一个例子: 系统崩溃前的一些现象:
new Thread() {
public void run() {
while(true){
String test = new String("test");
System.out.println(test);
}
}
}.start();
new Thread() {
public void run() {
while(true){
String test1 = new String("test1");
System.out.println(test1);
}
}
}.start();
3、软件分析
main方法执行后
(1)、监控,,可以看到堆和持久代使用情况
(2)、线程分析
main方法中的两个线程,可以看到两个线程实时上下文切换
3、visual GC分析
年轻代(eden, Survivor1, Survivor2),年老代,持久代,垃圾回收次数,垃圾回收时间
4、Profiler 中的CPU和内存具体查看执行方法
CPU查看具体执行方法
内存具体查看
重点1:如果满足下面的指标,则一般不需要进行GC:
其他博客参考:
重点2:调优具体使用:
serial是工作在新生代,采用的是复制算法;
serialOld是工作在老年代,采用的是标记整理算法
缺点:STW时间较长(指的是Gc事件发生过程中,会产生应用程序的停顿)
优点:简单又高效,没有线程交互的消耗,收集效率高。
吞吐量优先垃圾回收器--》Parallel收集器
多线程的垃圾回收器,注重的是单位时间内垃圾回收的STW时间最短。需要多核CPU支持,是和在服务器中 使用。
像 Serial 收集器一样,Parallel 收集器也使用“stop the world”方法。这意味着,当垃圾收集器运行时,应用程序线程会停止。但是不同的是,Parallel 收集器运行时有多个线程执行垃圾收集操作。这种类型的垃圾收集器适用于在多线程和多处理器环境中运行中到大型数据集的应用程序
这是 JVM 中的默认垃圾收集器,也被称为吞吐量收集器
开启吞吐量优先垃圾回收器的参数:-XX:+Use Parallel GC ~ -XX:+Use Parallel Old GC
(Parallel:并行的)在JDK8是默认开启了这两个开关。当你手动开启其中一个开关,另一个也会默认开启。
优点:单位STW时间较短
缺点:垃圾回收时,CPU占用率过高
响应时间优先垃圾回收器--》CMS GC和ParNewGC
多线程的的垃圾回收器,注重的是尽可能让单次垃圾回收中STW的时间最短,在垃圾回收时,不需要其它线程暂停,可以和其它用户线程并发(concurrent)执行是多个线程和垃圾回收线程抢占CPU。
开启响应时间优先垃圾回收器的参数:-XX: +UseConcMarkSweepGC~-XX: +UseParNewGC~ Serial0ld
优点:并发收集、低停顿
缺点:垃圾回收占用了一整个线程,整个程序的吞吐量降低。会产生内存碎片。
G1(Garbage First)收集器
G1 垃圾收集器旨在替代 GMS。G1 垃圾收集器具备并行、并发以及增量压缩,且暂停时间较短。与 CMS 收集器使用的内存布局不同,G1 收集器将堆内存划分为大小相同的区域,通过多个线程触发全局标记阶段。标记阶段完成后,G1 知道哪个区域可能大部分是空的,并首选该区域作为清除/删除阶段。
在 G1 收集器中,一个对象如果大小超过半个区域容量会被认为是一个“大对象” 。这些对象被放置在老年代中,在一个被称为“humongous region”的区域中。
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 同时注重吞吐量(Throughput) 和低延迟(Low latency) .
整体上是标记+整理算法,两个区域之间是使用复制算法
超大堆内存,会将堆划分为多个大小相等的区域Region(每个区都有自己新生代和老年代)
开启G1垃圾回收器的参数:- XX: +UseG1GC 在JDK9之前需要手动启用G1回收
初始标记:在Young GC时会进行GC Root的初始标记
并发标记:老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),
JVM参数决定: -XX:InitiatingHeapOccupancyPercent = percent (默认45%),最终标记:对E、S、O区域进行全面垃圾回收,会STW
筛选回收:
具体的回收步骤:
将E区和S区的无用对象进行回收,有用对象进行复制算法到一个新的S区。
将符合晋升阈值的对象加入到老年代中。
当现有的O区中内存已满,就会根据暂停时间,在暂停时间内,有选择的将O区中的对象复制到新的O区中。(优先回收能释放出更多内存的区域,所以叫做Garbage First)
Serial Old 垃圾回收器
Serial Old 和Serial的工作模式一样,唯一区别是它采用了标记-整理垃圾回收算法,主要是为老年代垃圾回收服务。
ParNew Old 垃圾回收器
ParNew Old 和ParNew 的工作模式一样,唯一区别是它采用了标记-整理垃圾回收算法,主要是为老年代垃圾回收服务。
新生代和老年代分类: ·
新生代收集器: Serial、ParNew、Parallel Scavenge
老年代收集器: Serial Old、Parallel Old、CMS
整堆收集器:G1
算法:
标记复制算法:Serial、ParNew、Parallel Scavenge、G1
标记清除算法:CMS
标记整理算法:Serial Old、Parallel Old、G1
重点4:内存溢出和内存泄漏的区别?
内存泄漏memory leak :指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
内存溢出 out of memory :是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory
内存泄漏可以分为4类:
内存溢出原因:
1、启动参数内存值设定的过小
2、内存中加载的数据量过于庞大
3、代码中存在死循环或者递归中创建对象
避免内存溢出
1、尽早释放无用内存
2、处理字符串尽可能使用StringBuffer,因为每创建一个String占一个独立内存
3、避免死循环或者递归中创建对象
4、修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
性能调优