JVM知识整理
2021-03-07 19:51:17 980 举报
AI智能生成
登录查看完整内容
Java虚拟机(JVM)是Java技术的核心和基础,它是Java能实现“一次编写,到处执行”的关键。JVM是一个虚拟的计算机,它有自己的指令集和运行时内存区域。Java程序在运行之前,会被编译成字节码文件,然后由JVM加载并执行。JVM负责管理内存,包括堆内存的分配和回收,垃圾收集等。此外,JVM还提供了一些机制,如类加载器,保证Java程序的动态性和安全性。JVM的知识包括JVM的架构、内存模型、类加载机制、垃圾收集算法、调优策略等。掌握JVM知识,可以帮助我们更好地理解Java程序的运行机制,提高Java程序的性能和稳定性。
作者其他创作
大纲/内容
From Survivor区的大小
S0C
To Survivor区的大小
S1C
From Survivor区当前使用的内存大小
S0U
To Survivor区当前使用的内存大小
S1U
Eden区的大小
EC
Eden区当前使用的内存大小
EU
老年代的大小
OC
老年大当前使用的内存大小
OU
方法区(永久代,元数据区)的大小
MC
方法区(永久代,元数据区)当前使用的内存大小
MU
系统运行迄今为止Young GC的次数
YGC
Young GC的耗时
YGCT
系统运行迄今为止的Full GC次数
FGC
这是Full GC的耗时
FGCT
所有GC的总耗时
GCT
当前压缩类空间的容量 (字节)
CCSC
当前压缩类空间目前已使用空间 (字节)
CCSU
jstat -gc PID
查看一个java进程的内存和gc情况
堆内存分析
jstat -gccapacity PID
年轻代GC分析,这里的TT和MTT可以看到在年轻代存活的对象的年龄和最大年龄
jstat -gcnew PID
年轻代内存分析
jstat -gcnewcapacity PID
老年代GC分析
jstat -gcold PID
老年代内存分析
jstat -gcoldcapacity PID
元数据内存分析
jstat -gcmetacapacity PID
其他jstat命令
新生代对象增长的速率
Young GC触发频率
每次Young GC后有多少对象存活
每次Young GC有多少对象进入老年代
老年代对象增长速率
Full GC的触发频率
Full GC的耗时
分析线上JVM进程时想知道的信息
根据自己的系统灵活多变的使用,如果系统负载很低,就把时间调高去观察对象增长的速率。另外一般系统又高峰和日常两种状态,可以分时段去查看对象增长速率。
jstat -gc PID 1000 10
如何使用jstat工具
这个命令会打印出来内存的相关一些参数的设置,然后就是当前堆内存里面一些基本各个区域的情况,比如Eden区总容量,已经使用的容量,剩余的空间容量,两个Survivor区的总容量,已经使用的容量和剩余的空间容量,老年代的总容量,已经使用的容量和剩余容量。
jmap -heap PID
按照各种对象占用内存空间的大小降序排序,把占用内存最多的对象放在最上面。
jmap -histo PID
jmap命令生成一个堆内存快照放到当前目录的一个文件dump.hprof里面去,这是二进制的格式。
使用此命令即可启动jhat服务器,指定自己想要的http端口号,接着就可以在浏览器上访问当前这台机器的7000端口号,就可以通过图形化的方式去分析堆内存里的对象分布情况了。
jhat dump.hprof -port 7000
jmap/jhat
用来分析OOM时的内存快照
MAT
调优工具
这个非常有用,因为并不是每个人都比较精通JVM核心原理和性能优化,所以如果有一套为团队或者公司定制一套基本的JVM参数模板,基本可以保证JVM性能不会太差,避免许多初中级工程师直接使用默认的JVM参数,并不能满足生产需要。
意义
-Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:SurvivorRatio=8 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=10 -XX:PretenureSizeThreshold=2M -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:./gclog/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom
模板内容
4C8G JVM参数模板
Metaspace区域内存空间不够,但是还要往这里塞入更多类的时候,就会发生内存溢出的问题。
系统上线时,Metaspace直接使用的默认的参数,而默认参数往往比较小,很容易不够用。
代码中使用cglib之类的技术生成类,没有控制好,导致生成的类过多,也容易塞满Metaspace,导致内存溢出。
原因
合理分配Metaspace区域
避免无限生成动态类。
解决方法
元空间溢出
如果不停的让线程调用方法,不停的往栈里面放入栈帧,最终会有一个时刻,大量栈帧会消耗完这个栈内存,最终就会出现栈内存溢出的情况。
避免不合理的调用递归方法等
栈内存溢出
系统承载高并发请求,因为请求量过大,导致大量对象是存活的,所以要继续放入新的对象实在不行了,此时就会引发OOM系统崩溃。
系统有内存泄漏的问题,就是莫名其妙弄了很多对象,结果对象都是存活的,没有及时取消对他们的引用,导致触发GC还是无法回收,此时只能引发内存溢出,因为内存实在放不下更多对象了。
堆内存溢出
内存设置不合理,导致DirectByteBuffer对象一直慢慢进入老年代,导致堆外内存一直释放不掉。
设置了-XX:+DisableExplicitGC导致Java NIO没法主动提醒去回收掉一些垃圾DirectByteBuffer对象,同样导致堆外内存无法释放掉。
直接内存溢出 Direct Buffer Memory
内存溢出的处理
代码中包含main()方法的主类再JVM进程启动后会被加载到内存,开始执行其中代码时,若其中使用了别的类,也会把其对应的“.class”文件加载到内存里。
加载时机
加载
根据java虚拟机规范,校验加载的\".class\"文件是否符合指定的规范
验证
给类分配一定内存空间,并赋予默认初始值
准备
把符号引用替换成直接引用
解析
当新建某个类的对象时,如通过new或者反射,克隆,反序列化等
当虚拟机启动某个被表明为启动类的类,即包含main方法的那个类,所以system.out.println(Test.class)
初始化一个子类的时候,会先初始化其父类
当调用某个类的静态方法时
当使用某个类或者接口的静态字段时
调用Java API中的某些反射方法时,比如类Class中的方法,或者java.lang.reflect中类的方法时
触发初始化时机
初始化
使用
卸载
类加载,使用的过程
加载jdk安装目录下lib目录中的核心类库
启动类加载器 Bootstrap ClassLoader
加载jdk安装目录下lib\\ext目录
扩展类加载器 Extension ClassLoader
加载ClassPath环境变量中指定路径中的类,可以理解为加载你写的代码
应用程序加载器 Application ClassLoader
自定义类加载器
类加载器的分类
假设应用程序类需要加载一个类,首先会委派给父类加载器加载,最总传导到顶层的类加载器去加载。但如果父类加载器在自己负责的加载范围内没找到这个类,就会下推权力给自己的子类加载器。
图示
双亲委派机制
Tomcat这种web容器中类加载器设计实现思路。
类加载器和双亲委派机制
编译时,可采用小工具对字节码进行加密,或者做混淆处理
购买专业的第三方公司做商业级字节码文件加密的产品。
如何防止“.class”被反编译窃取公司源码
类加载
JVM在运行我们写好的代码时,需要使用多块内存空间,不同的内存空间用来存放不同的数据,然后配合我们写代码的流程,才能让我的系统运行起来。
存放我们自己写的各种类的信息
jdk1.8后也称元数据空间 Metaspace
方法区
存放我们在代码中创建的各种对象的
Eden区
Survivor(from)区 设置survivor是为了减少送到老年代的对象
Survivor(to)区 设置两个Survivor区是为了解决碎片问题
区域划分
eden:survovir:survivor=8:1:1
划分比例
新生代
老年代
堆内存的划分
堆内存
线程共享数据区域
我们写好的java代码会被翻译成字节码,对应各种字节码指令,字节码指令一条条被执行,才能实现我们写好的代码的执行效果。当JVM加载类信息到内存之后,会使用自己的字节码执行引擎,去执行编译后的代码指令。执行时,JVM需要一个特殊的内存区域,程序计数器(用来记录当前执行字节码指令位置的,每一个线程都会有自己的程序计数器),记录目前执行到哪一条字节码指令。
程序计数器
每个线程都有自己的java虚拟机栈,用来存放自己执行的那些方法的局部变量,执行了一个方法,就创建一个栈帧。栈帧里就有这个方法的局部变量表,操作数栈,动态链接,方法出口等东西。
调用执行任何方法时,都会给方法创建栈帧后入栈,在栈帧里存放了这个方法对应的局部变量之类的数据,包括这个方法执行的其他相关信息,方法执行完毕后出栈。
虚拟机栈
调用native方法的时候,就会有线程对应的本地方法栈。这个虚拟机栈类似,是存放各种native方法的局部变量表之类的信息。
本地方法栈
线程私有数据区域
还有一个区域,不属于JVM,通过NIO中的allocateDirect这种API,可以在java堆外分配内存空间,然后,通过java虚拟机里面的DirectByteBuffer来引用和操作堆外的空间。
拓展
详情
内存区域划分
我们在java堆内存里创建的对象,都是占用内存资源的,而且内存资源有限。
一个方法执行完毕,就会从虚拟机中出栈,那么原来那个栈帧里面的局部变量,比如一个实例对象,就没有任何一个变量指向它了。这种空占着资源着的内存资源,如何处理?答案是通过 JVM的垃圾回收机制。
举例
对象本身的一些信息
对象的实例变量作为数据占用的空间
我们创建的对象,在java堆内存中占用多少内存空间?
内存的消耗
Java堆内存的大小
-Xms
Java堆内存最大大小
-Xmx
Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了
-Xmn
JDK1.8以后,这个参数被替换成了-XX:MetaspaceSize
永久代最大大小
-XX:PermSize
JDK1.8以后,这个参数被替换成了-XX:MaxMetaspaceSize
-XX:MaxPermSize
每个线程的栈内存大小
-Xss
定义了新生代中Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10
-XX:SurvivorRatio=8
设置JVM栈内存
-XX:ThreadStackSize
在OOM的时候,自动dump内存快照出来
-XX:+HeapDumpOnOutOfMemoryError
在OOM时候,dump内存快照保存位置
-XX:HeapDumpPath
内存相关参数
JVM内存区
大部分正常对象都优先在新生代分配内存
新生代如果对象满了,会触发Minor GC回收掉没人引用的垃圾对象
JVM参数:-XX:MaxTenuringThreshold,默认值15
默认设置下,当对象年龄达到15岁的时候,也就是躲过15次GC的时候,会转移到老年代里去。
如果老年代也满了,那么就会触发垃圾回收,把老年代里没人引用的垃圾对象清理掉
新生代垃圾回收(Minor GC)之后,如果存活对象太多,超过survivor区的大小,就会导致大量对象直接进入老年代
如果新生代里创建了一个大于这个大小的对象,就把这个大对象直接放到老年代。之所以这么做时为了避免新生代出现的那种大对象,多次躲过GC,导致其在两个Survivor区域里来回复制多次后才能进入老年代。
参数:-XX:PretenureSizeThreshold可以把他的数值设置为字节数。
特别大的超大对象直接不经过新生代就进入老年代
例如说当前对象的Survivor区域里,一批对象(年龄可以不同)的总大小大于了这块Survivor区域的内存大小的50%,那么此时大于等于这批对象的年龄的对象,就可以直接进入老年代。
动态对象年龄判断机制
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的,如果担保失败则会进行一次Full GC;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。
老年代空间担保机制
对象分配机制
该类所有的实例都已被回收
该类的ClassLoader已经被回收。
对该类的Class对象没有任何引用。
类被回收的条件
局部变量(栈中)
静态变量
GC Roots
可达性分析算法
定义:一个变量引用一个对象
只要是强引用类型,那么垃圾回收的时候绝对不会去回收这个对象。
强引用
定义:把实例对象用一个“SoftReference”软引用类型的对象包裹起来了,赋值给一个变量,这时这个变量对这个对象就是一个软引用。
正常情况下垃圾回收是不会回收软引用对象的,但是如果你进行垃圾回收之后,发现内存空间还是不够存放新的对象,内存都快溢出了,此时就会把这些软引用对象给回收掉,哪怕他被变量引用了,但是因为他是软引用,所以还是要回收。
软引用
定义:WeakReference 弱应用类型
弱应用就和没引用是类似的,如果发生垃圾回收,就把这个对象回收掉
弱引用
虚引用
不同的引用类型
确定垃圾
Stop the world
垃圾回收的痛点
在Minor GC之前,一通检查发现可能Minor GC之后要进入老年代的对象太多了,老年代放不下,此时要提前触发Full GC,然后再进行Minor GC
或者在Minor GC之后,发现剩余对象太多,放入老年代都放不下了。
老年代垃圾回收时机
把新生代内存划分为两块内存区域,然后只使用其中一块内存,待那块内存快满的时候,就把里面的存活对象一次性转移到另一块内存区域,保证没有内存碎片。接着一次性回收原来那块内存区域内的垃圾对象,再次空出来一块内存区域,两块内存区域就这么重复着循环使用。
减少内存空间碎片造成的空间浪费
优点
从始至终,只有一半的内存可用,这样的算法显然对内存的使用效率太低了。
缺点分析
针对上述缺点,真正复制算法把新生代内存区分为三块,1个Eden区,2个Survivor区,其中Eden区占80%内存,每一块Survivor占用10%的内存。对象内存分配的时候,只在Eden区和其中一块survivor区上分配,回收时,将存活对象复制到另一块survivor区。这样做最大的好处就是只有10%的内存空间是被闲置的,90%的内存都被使用上了。无论垃圾回收的性能还是内存碎片的控制,内存的使用效率,都非常的好。
复制算法的优化
复制算法
标记出来老年代当前存活对象
让这些存活对象在内存里进行移动,把存活对象尽量都挪动到一边去,把存活对象紧凑的靠在一起,避免垃圾回收过后出现太多碎片
一次性把垃圾回收掉
步骤
这个老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法速度慢10倍,如果系统频发的出现老年代Full GC垃圾回收,会导致系统性能被严重影响,出现频发卡顿的情况。
缺点
所谓JVM优化,就是尽可能让对象都在新生代里分配和回收,尽量别让太多对象进入老年代,避免频繁对老年代对象进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁进行垃圾回收。
应对思路
标记整理算法
将所有待回收的对象标记出来,然后一起清理
1.标记和清理的效率都不高
2.可能产生大量的内存碎片
标记-清除算法
根据不同的年代选取不同的垃圾回收算法
分代收集算法
垃圾回收算法
用一个线程进行垃圾回收,此时会暂停系统工作线程
用的少
适用于单线程回收
Serial
回收老年代
Serial Old
单线程运行,垃圾回收的时候会停止我们自己写的系统的其他工作线程,让我们的系统直接卡死不动,然后让他们垃圾回收,这个现在一般不用。
工作原理
Serial/Serial Old
针对服务器一般都是多核进行了优化,支持多线程垃圾回收,可以大幅度提升回收性能,缩短回收时间。
适用于多核多线程回收
可指定该垃圾收机器对新生代进行回收,一旦我们指定使用ParNew垃圾收集器后,它默认给自己设置的回收线程数和CPU核数一致。
-XX:+UseParNewGC
可设置线程数量
-XX:ParallelGCThreads
相关JVM参数说明
ParNew
当老年代内存占用达到一定比例了,就自动执行GC。
触发时机
为了解决Stop the world时,系统卡死时间过长,响应无法处理的问题,CMS垃圾收集器采取的是垃圾回收线程和系统工作线程尽量同时执行的模式来处理。
这个阶段会让系统工作线程全部停止,进入stop the world,然后标记出来所有GC Roots直接引用的对象。
速度很快
初始标记
这个阶段会让系统线程可以随意创建各种新对象,继续运行。也可能让部分存活对象失去引用,变成垃圾对象,在这个过程中,垃圾回收线程,会尽可能地对已有对象进行GC Roots追踪,找出被GC Roots 间接引用 的对象。
是最耗时的
并发标记
为了解决第二阶段,很多存活对象和垃圾对象没被标记出来的问题,进入此阶段时,要继续让系统停下来,再次进入stop the world 阶段,重新标记第二阶段里新创建的对象,还有一些可能失去引用而变成垃圾的对象。
因为只是对少数在第二阶段中被系统程序运行变动过的对象进行标记,所以运行速度很快。
重新标记
让系统程序随意运行,然后清理掉之前标记的为垃圾的对象即可。
这个阶段其实很耗时,因为需要对对象进行清理,但是与系统程序并发运行,不影响系统程序的执行。
并发清理
垃圾回收过程
第二阶段和第四阶段非常耗费CPU资源,CMS默认启动的垃圾回收线程数量是(CPU核数 + 3)/ 4,当CPU核数比较少时,这个问题尤为严重。
在第四阶段,CMS只不过回收之前标记好的垃圾对象,但是这个阶段系统一直在运行,可能会随着系统运行让一些对象进入老年代,同时还变成垃圾对象。这种垃圾对象是浮动垃圾。但是CMS只能回收之前标记出来的垃圾对象,不会回收他们,需要等到下一次GC的时候,才会回收他们。所以为了保证在CMS垃圾回收期间,还有一定的内存空间可以让一些对象进入老年代,一般会预留一些空间。
如果CMS垃圾回收期间,系统程序放入老年代的对象,大于了可用内存空间,就会发生Concurrent Mode Failure,内存不够了。
这时就会自动用Serial Old 垃圾回收器替代CMS,就是直接强行把系统程序Stop the world,重新进行长时间的GC Root追踪,标记出来全部的垃圾对象,不允许新的对象产生。然后一次性把垃圾对象都回收掉,完事了,再恢复系统线程。
Concurrent Mode Failure的问题
-XX:+UseCMSCompactAtFullCollection
执行多少次Full GC之后进行一次内存碎片整理的工作,默认是0
-XX:CMSFullGCsBeforeCompation
这个参数会在CMS重新标记阶段之前,先尽量执行一次Young GC,提前回收掉一些没人引用的对象,这样在CMS的重新标记阶段就可以少扫描一些对象,此时就可以提升这个阶段的性能,减少耗时。
-XX:+CMSScavengeBeforeRemark
开启CMS垃圾回收器
-XX:+UseConcMarkSweepGC
在CMS垃圾回收器的重新标记阶段开启多线程并发执行,减少stop the world
-XX:+CMSParallelRemarkEnabled
在CMS垃圾回收器的初始标记阶段开启多线程并发执行,减少stop the world。
-XX:+CMSParallelInitialMarkEnabled
这个参数可以用来设置老年代占用多少比例的时候触发CMS垃圾回收,JDK1.6里面默认值是92%。预留8%的空间给并发回收期间,系统程序把一些新对象放入老年代中。
如果老年代可用内存大于历次新生代GC后进入老年代的对象的平均大小,但是老年代已经使用的内存空间超过了这个参数的比例,也会触发Full GC
-XX:CMSInitiatingOccupancyFaction
和前面的参数配套使用,如果不设置后者,jvm第一次会采用设置的值,比如92%,如果设置后者,则jvm每次都在92%的时候进行GC。
-XX:+UseCMSInitiatingOccupancyOnly
相关参数设置
CMS
ParNew/CMS垃圾回收器
把Java堆内存拆分为多个大小相等的Region
也有新生代和老年代的概念,不过是逻辑上的概念,也就是说,新生代可能包含了某些region,老年代可能包含某些region,且新生代和老年代各自内存区域在不停变动,由G1控制。
追踪每个region中可以回收的对象的大小和预估时间,最后在垃圾回收的时候,尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,尽可能在有限地时间内尽量回收尽可能多的垃圾对象。
设置一个垃圾回收的预期停顿时间
触发垃圾回收的时候,可根据设定的预期系统停顿(stop the world)时间,来选择最少回收时间和最多回收对象的region进行垃圾回收。保证GC对系统停顿的影响控制在可控范围内,同时还能尽可能回收最多的对象。
触发垃圾回收的机制是类似的
特点
指定G1垃圾回收器,此时会自动用堆大小除以2048,因为JVM最多有2048个region,region大小必须是2的倍数。
-XX:+UseG1GC
手动指定region大小
-XX:G1HeapRegionSize
设置新生代初始占比,默认5%,这个维持默认值即可。
-XX:G1NewSizePercent
在系统运行中,JVM会不停给新生代增加更多Region,但最多新生代占比不会超过60%,可通过此参数设置这个百分比。
-XX:G1MaxNewSizePercent
最多可以让系统停顿多长时间。
-XX:MaxGCPauseMills
新生代进入老年代的年龄。
-XX:MaxTenuringThreshold
eden区在整个新生代中的占比,默认值为8
-XX:SurvivorRatio
如果老年代占据了堆内存45%的时候,此时就会尝试触发一个新生代+老年代一起回收的混合回收阶段。
-XX:InitiatingHeapOccupancyPercent
在一次混合回收的过程中,最后一个阶段执行几次混合回收,默认是8次
-XX:G1MixedGCCountTarget
在混合回收的时候,对Region的回收都是基于复制算法进行的,都是要把回收的里的存活对象放入其他Region,然后这个里面的垃圾对象全部清理掉。这个的话,回收过程就会不断空出来Region,一旦空闲出来的数量达到了堆内存的5%。此时就会立即停止混合回收,意味着本次混合回收就结束了。
-XX:G1HeapWastePercent
默认值是85%,意思是确定要回收region的时候,必须存活对象低于85%的region才可以进行回收。否则复制成本会很高。
-XX:G1MixedGCLiveThresholdPercent
相关参数配置
1.对象在新生代躲过了多次垃圾回收,达到一定年龄了(如上,可设置),进入老年代。
2.动态年龄判断规则,一旦发现某次新生代GC过后,一批存活对象超过了Survivor的50%,大于这批对象年龄的对象就会进入老年代。
对象进入老年代的条件
在G1中,大对象的判定规则就是一个大对象超过了一个region大小的50%,如果一个对象太大,可能会横跨多个来存放。
新生代,老年代在回收的时候,会顺带带着大对象一起回收。
大对象region
允许系统程序的运行,同时进行GC Roots追踪,从GC Roots追踪所有存活对象。还会堆对象做出的一些修改记录起来,比如哪个对象被新建了,哪个对象失去了引用。
很耗时
会进入stop the world,系统程序是禁止运行的,但是会根据并发标记阶段记录的哪些对象修改,最终标记一下有哪些存活对象,有哪些垃圾对象。
最终标记
混合回收
如果在进行Mixed回收的时候,无论在年轻代还是老年代都是基于复制算法进行回收,都要把各个region的存活对象拷贝到别的region里面去,此时万一出现拷贝的过程中发现没有空闲的region可以承载自己的存活对象了,就会触发一次失败。一旦失败,就会立马切换为停止系统程序,采用单线程进行标记,清理,和压缩整理,空闲出来一批region,这个过程极慢极慢。
回收失败应对策略
G1
垃圾回收器
一,避免新生代通过复制算法回收后,survivor区放不下直接进入老年代的问题,这个可以通过合理配置eden区和survivor区的比例,尽量让survivor区有足够空间去容纳新生代回收后的对象。
二,规避young gc后,某一批次新生代存活对象在survivor区占比达到50%的情况,由于大于这批对象年龄的对象会直接进入老年代。所以要尽量避免这种情况的发生。
由于新生代存活对象少,速度较快,老年代存活对象多,需要进行GC Roots追踪的对象量大,且使用的算法需要进入stop the world,非常耗时。所以JVM优化的思路主要应该集中于在使对象在新生代就得到回收,避免他们快速进入老年代,引发Full GC。这个可以从两个方面下手。
老年代如果一直有大量对象无法回收掉,年轻升入老年代的对象并不多,频繁触发Full GC时,那就用dump出来内存快照,然后用MAT工具分析。
大对象如果在Young GC时候,老在新生代内存空间中挪来挪去也很消耗cpu资源,所以可以让这些长寿的大对象超过一定阈值时,直接进入老年代。
Metaspace永久代也可能因为加载太多的类触发Full GC,所以要避免一次加载太多类到内存中,或者合理设置-Xss参数值。
错误使用System.gc()也可能频繁触发Full GC,可以在jvm参数中加入-XX:DisableExplicitGC,禁止显示的执行gc,不允许通过代码触发GC.
对于一般机器来讲,parnew+cms的gc回收方案是很受用的,但是对于大内存机器来讲,由于使用此方案每次gc会产生大量的对象,系统停顿的时间太长,导致系统应用线程无法正常处理业务,所以最好使用G1回收器,可以有效缩短停顿时间,保证系统可用性。
老年代自身有一个设置其空间占用达到一定比例可以触发Full GC的阈值,可以适当调大一些。
优化思路总结
打印详细的Gc日志
-XX:+PrintGCDetails
打印每次的gc的时间
-XX:+PrintGCTimeStamps
将gc日志写入一个磁盘文件
-Xloggc:gc.log
分别打印GC前后的堆内存的情况
-XX:+PrintHeapAtGC
日志分析相关参数
追踪类加载的情况
-XX:TraceClassLoading
追踪类卸载的情况
-XX:TraceClassUnloading
类加载/卸载相关
clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB
clock记录是上一次GC的时间戳,timestamp则是最近一次读取soft-reference引用对象(即最近调用get())的时间戳。他们的差【clock - timestamp】表示了soft-reference有多久没用了,越大表示越久没用。如果他们的差为负数,表示刚刚用过。而【freespace * SoftRefLRUPolicyMSPerMB】表示能够VM的忍耐度,VM能够忍耐软引用对象多久没有被回收,而VM的忍耐度从公式可以知道是由VM计算得出的空闲空间大小和用户指定的忍耐度SoftRefLRUPolicyMSPerMB来决定的。也就是说,如果软引用上次被get()的时间离最近一次GC的时间不会太久远的话就可以不被当前GC回收。
SoftRefLRUPolicyMSPerMB
其他
其他参数
回收垃圾
垃圾回收两部曲
通过重写finalize()方法,可以使得没被GC Roots引用的对象不被回收
finalize()方法
垃圾回收机制
JVM知识整理
0 条评论
回复 删除
下一页