本文共 6725 字,大约阅读时间需要 22 分钟。
C++的内存回收很麻烦,不回收可能会造成内存泄漏,Java中由GC完成内存回收,不用手动回收内存。堆是垃圾回收的重点区域。
对象被判定为垃圾的标准:没有被其它对象引用
堆中每个对象都对应一个引用计数器,当一个变量引用此对象时,计数器+1;当引用此对象的变量生命周期结束或者被赋新值时,计数器-1;计数器为0时该对象成为垃圾,等待gc回收。
优点:简单、高效
缺点:需要存储每个对象对应的引用计数器,有额外的内存开销;没有处理循环引用问题,存在循环引用时,计数器永不为0,可能导致内存泄漏。
eg. a对象中引用了b对象,b对象中引用了a对象,即存在循环引用,使用引用计数法时,这2个对象的计数器永不为0,永远不会被回收。
即使把这2个变量都置为null,只是引用变成了null,堆中的这2个对象不变,依然持有彼此的引用,它们的计数器也不会为0。 因为引用计数法没有解决循环引用问题,所有主流垃圾收集器都不采用引用计数算法,而采用可达性分析算法。
又叫做根搜索算法,使用不同的GC Root,从GC Root开始寻找引用链上的对象,没在任何一条引用链中的对象标记为垃圾。
相比于引用计数法,可达性分析算法同样具备简单、高效的优点,且没有存在循环引用时不能被gc回收的问题。
可以作为GC Root的对象
使用可达性分析算法进行分析时,整个分析过程必须在一个一致的堆内存快照中进行,否则不能保证分析结果的正确性,这也是进行gc时必须stop the world的一个重要原因。
即使是以系统最短停顿时间为目标的CMS收集器,枚举GC Root时也是要停顿的。
对象的finalization机制
一个没被任何其它对象引用的对象,只是暂时不被使用,并不一定就是垃圾,可能该类重写了finalization()方法,在finalize()方法中复活、重新使用当前对象。
堆中对象可能的三种状态
可达性分析算法至少要经过2次标记,才会把对象标记为垃圾
如果对象所属的类没有重写finalize()方法,或者之前已经执行过finalize()方法但没有复活对象,则直接标记为不可触及的;
如果对象所属的类重写了finalize()方法,且没有执行过finalize()方法,则把对象放入finalize队列中, 由jvm创建的一个低优先级的finalizer线程处理队列中对象,调用对象的finalize()方法,如果没有复活,则把对象标记为不可触及的。
状态为不可触及的对象才会成为垃圾,等待被gc回收。
清除并不是置空,只是把要清除的对象的地址保存在空闲地址列表中,后续分配内存时可以使用这些内存,再次分配时才覆盖原有内容。
缺点:容易产生内存碎片,可能导致后续给大对象分配内存空间时没有足够大的连续内存,从而提前触发下一次gc。
不会产生内存碎片,但效率要低于复制算法。
将内存划分为2块,每次只使用一块,进行垃圾回收时将存活的对象复制到未使用的一块上,清理掉之前使用的那一块。
不会产生内存碎片,适合对象存活率低的场景,常用于新生代的垃圾回收。
新生代对象存活率低,一般使用复制算法;老年代对象存活率高,一般使用标记-整理算法。
把堆划分为2大块
对象的分配规则
垃圾回收方式
gc分类
触发full gc的条件
分配担保:新生代对象存活率偏高,Survivor to中放不下时,会使用老年代的空间进行分配担保,即把Survivor to中放不下的对象直接放到老年代中。
full gc时间花销大,造成的停顿时间较长,看到jvm频繁进行full gc时要引起注意,应该进行优化。
对象如何晋升到老年代
stop the word:jvm进行垃圾回收时会暂停应用程序的执行(暂停所有用户线程),gc完成才会继续执行应用程序,主流垃圾收集器或多或少都存在这各个情况。
safepoint:安全点,标记阶段对象引用关系不会发生变化的点,比如方法调用、循环跳转处。
使用java -version可以查看jvm的种类、运行模式,HotSpot默认使用server模式。
Par是Parallel的缩写
#使用ParNew收集器,+是启用,-是取消-XX:+UseParNewGC#可以指定进行垃圾回收的线程数,默认为cpu核心数-XX:ParallelGCThreads=8
与其它收集器不同,其它收集器关注缩短系统停顿时间,而 Parallel Scavenge关注吞吐量
吞吐量 = 运行用户代码的时间 / (运行用户代码的时间 + 垃圾收集时间)
高吞吐量可以高效利用cpu执行程序代码,适合在不需要太多交互的应用中使用。
Parallel Scavenge可以使用gc自适应调节:jvm根据当前运行状况,动态调整设置最适合的gc停顿时间、吞吐量
#启用gc自适应调节-XX:UseAdaptiveSizePolicy#也可以使用以下方式进行手动设置,但一般不手动设置#设置gc最大暂停时间,在这个时间范围内,至少进行一次gc-XX:MaxGCPauseMillis=600000#设置吞吐量,默认99,即吞吐量为99%-XX:GCTimeRatio=99
#取消默认的Parallel Old收集器-XX:-UseParallelOldGC
web服务端重视响应速度,希望gc引起的系统停顿时间尽可能短,以带给用户更好的体验。
CMS只在初始标记阶段出现stop the world,其它回收阶段可以和用户线程并发执行,回收垃圾引起的系统停顿时间几乎可以忽略不计,是server模式下主流的老年代垃圾收集器。
#启用CMS收集器,CMS是ConcMarkSweep的缩写-XX:+UseConcMarkSweepGC
优点:系统停顿时间短
缺点:使用标记-清除算法,容易产生大量的内存碎片。
G1 会跟踪各个 Region 里面垃圾的回收价值,记录回收所获得的空间、所花费的时间,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
这也是 Garbage- Firsti 名称的由来、以及可预测的原因,这种方式保证了 G1 在有限时间内获取尽可能高的回收价值。‘
名称 | 收集年代 | 使用的垃圾回收算法 | gc线程 | 关注点 | 地位 |
---|---|---|---|---|---|
Serial收集器 | 新生代 | 复制算法 | 单线程 | client模式下默认的新生代收集器 | |
ParNew收集器 | 新生代 | 复制算法 | 多线程 | 新生代主流使用的收集器 | |
Parallel Scavenge收集器 | 新生代 | 复制算法 | 多线程 | 关注吞吐量 | server模式下默认的新生代收集器 |
Serial Old收集器 | 老年代 | 标记整理算法 | 单线程 | client模式下默认的老年代收集器 | |
Parallel Old收集器 | 老年代 | 标记整理算法 | 多线程 | 关注吞吐量 | server模式下默认的老年代收集器 |
CMS收集器 | 老年代 | 标记清除算法 | 单线程、多线程混合 | 关注缩短系统停顿时间 | 老年代主流使用的收集器 |
G1收集器 | 所有年代 | 标记整理算法 | 单线程、多线程混合 | 关注缩短系统停顿时间、高价值回收 | 优秀,但低版本jdk中包含的G1版本尚不成熟 |
java web应用的收集器选择
jvm内存不是越大越好,内存太小会频繁GC,内存太大触发GC时停顿时间会较长。根据压测结果不断调整jvm内存大小,合适就好,并非越大越好。
优化指标
#启动应用的时候可以设置jvm参数,单位直接用 k、m、g#在IDEA中同样可以设置jvm参数,参数之间都是用空格分隔java -Xms512m -Xmx512m -jar xxx.jar#控制台打印gc信息-verbose:gc -XX:+PrintGCDetails#一般将初始堆内存、最大堆内存设置为一样的,防止堆扩容时引起内存抖动、影响程序运行的稳定性-Xms10g #初始堆内存,默认为物理内存的1/64-Xmx10g #最大堆内存,默认为物理内存的1/4-Xss256k #每个线程 java虚拟机栈的大小-XX:MetaspaceSize=1g #元空间的初始内存#-XX:MaxMetaspaceSize #元空间的最大内存,默认不限制。jdk的元空间直接使用本地内存,不占用堆内存,不用指定元空间的最大内存。
主流的垃圾回收算法是分代收集算法,以下的jvm调优参数也是针对分代收集算法的
-Xmn512m #新生代大小-Xx:NewRatio=3 #老年代、新生代的大小比例,默认3-Xx:SurvivorRatio=8 #Eden区与一个Servivor区的大小比例,默认8。2个servivor区的大小比例默认1:1-XX:MaxTenuringThreshold=15 #新生代对象晋升到老年代需经历的Minor GC次数,默认15-XX:PretenureSizeThreshold=3145728 #大对象阈值,单位字节,体积超过这个值就认为是大对象,直接在老年代分配空间
#ParNew-XX:+UseParNewGC #启用ParNew收集器-XX:ParallelGCThreads=n #ParNew回收垃圾使用的线程数。默认为cpu核心数,但使用docker部署应用时,可能取的是物理机的cpu核数,而非分配给容器的cpu核数,最好手动指定,避免踩坑#CMS-XX:+UseConcMarkSweepGC #启用CMS收集器,CMS是ConcMarkSweep的缩写-XX:ParallelCMSThreads=n #CMS回收垃圾使用的线程数,和ParNew的一样,在docker下容易踩坑,应该手动配置-XX:+UseCMSCompactAtFullCollection #在FullGC后压缩整理内存碎片-XX:CMSFullGCBeforeCompaction=4 #每隔4次FullGC才整理压缩一次内存碎片-XX:UseCMSInitiatingOccupancyOnly #使用内存占用阈值触发GC-XX:CMSInitiatingOccupancyFraction=70 #(老年代)内存占用达到70%就触发GC#G1-XX:+UseG1GC #启用G1收集器-XX:MaxGCPauseMillis=n #GC最大停顿时间,G1会尽可能满足这个参数-XX:G1HeapReginSize=n #每个Regin的大小
这些jvm调优参数并非1次就确定下来,需要不断调整参数,反复测试查看GC日志,比较效果以找到合适的值。
转载地址:http://zkhlb.baihongyu.com/