JVM相关知识点很多,总结了一部分,全是干货。
JVM内部存储
JVM内存区域分为虚拟机栈、本地方法栈、堆、方法区、程序计数器
虚拟机栈 | 局部变量、基本数据类型、对象引用、和字节码指令地址 、为java方法服务 |
本地方法栈 | 为本地方法服务 |
堆(主要探讨) | 内存 |
方法区 | 类、常量、静态变量,线程内存 |
程序计数器 | 线程位置的计数器 |
内存分配机制
内存分配主要指堆中的内存分配,也是GC主要区域
堆 (-Xms -Xmx)(Java Heap) 以分代法为基础,将内存分为:
新生代(Young generation -Xmn): 新生对象放在年轻代里,尽可能快速收集生命周期短的对象
分为三个区:Eden、两个Survivor。Eden满了给 Survivor,Survivor满了会把从另一个Survivor复制过来的且存活的对象 复制到年老区(该过程为一次Minor GC, 单纯Survivor 满不会引发 Minor GC)。两个Survivor区是对称的,可能同时有Eden 获取的对象。而年老区的对象都是只有从第一个Survivor取过来的,Survivor区总有一个是空的。Survivor可以配置多个,这样可增加新生代,减少沦为OG的可能性
老年代(OG -XX):经历N次垃圾回收仍然存活的对象。OG存的都是生命周期比较长的对象。
持久代(JDK1.8后移除,转为元空间):存放静态文件,对GC没有显著影响,可能会动态生成或调用class。因为其使用堆外内存,也就是直接内存,元空间的最大可分配空间就是系统可用内存空间,可以避免永久代的内存溢出问题
Minor GC : 对象在 Survivor 区中每熬过一次 Minor GC,Age增加一岁,当达到阈值时 GC可转为老年代 (该阈值 : -XX:MaxTenuringThreshold)
Full GC:当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代,
当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载
参数调优
JVM参数调优主要针对减少FullGC次数和对持续时间的优化。
引入系统吞吐量概念(进程可用时间/运行总时间)
进程可用时间是指进程运行总时间减去安全点暂停时间。
FullGC是安全点暂停时间较长的主要原因:
堆内存YG:OG=1:2当局部变量远远大于全局变量时,局部变量生命周期都很短。需重新配置
-Xmx:-Xms -XMx 设置初始/最大堆大小。xmx越大。持续时间越长。xmx越小,FullGC次数越多
YG优化:小对象避免发往OG,------合理设置晋升年龄范围。
默认Eden:s1:s2 = 8:1:1 年轻一点MinorGC较老一代 MinorGC更频繁,但是速度较快,所以 短寿命的尽量留在GC收回。若s比较太小可能导致对象直接晋升OG。可以设置SurvivorRatio ,对象达到S区一半时,晋升年龄更大。 大对象直接放给OG,设置PretenurSizeThreshold = 1024,超过1k对象直接OG
OG优化:
OG默认使用Parallel-old垃圾收集器,采用标记清除算法,安全天暂停线程,然后多线程并行GC 会造成时许时间过长。通常启用CMS收集器代替。(特点:GC线程与用户线程同步进行)
总结优化阈值:
1. YG中 Age 计数参数 MaxTenuringThreshold :控制新生代需要经历多少次GC晋升到老年代中的最大阈值
2. 对象大小参数 PretenurSizeThreshold : 超过1k对象直接OG
3. survivor 大小参数 :SurvivorRatio :
4. survivor 个数: Survivor可以配置多个,这样可增加新生代,减少沦为OG的可能性
GC机制
GC有两种:并行、串行
GC机制是由JVM一个被称为垃圾回收器的守护线程执行,用来清理需要清除的对象,回收堆内存。
在从内存回收一个对象之前会调用对象的finalize()方法。
当一个对象通过一系列根对象都不可达时就会被回收。(简而言之,当一个对象的所有引用都为null。循环依赖不算做引用,如果对象A有一个指向对象B的引用,对象B也有一个指向对象A的引用,除此之外,它们没有其他引用,那么对象A和对象B都、需要被回收)
GC 算法
1.引用计数法
解释:增加一个变量引用,引用计数器加一。减少一个引用,计数器减一。 当计数器为0时,GC回收
缺点:频繁加减法 会增加对系统资源的消耗
内存泄露:无法处理循环引用的情况。假设两个对象 A和B,A引用了B对象,并且B中也引用了A对象,
那么这时两个对象的引用计数器都不为0,但是由于存在相互引用导致无法垃圾回收A和 B,导致内存泄漏。
2.标记清除法
解释:标记阶段、清除阶段。标记阶段标记所有可达对象,清除未标记对象
缺点:垃圾回收后可能存在大量内存碎片。因为对象所占地址空间是固定的
3.标记压缩清除法
解释:标记、压缩、清除。标记后的对象放在一起,确定开始和结束地址。集体清除不会有遗留的内存碎片。
缺点:压缩阶段占用系统消耗。标记过多,效率会下降
4.复制算法(新生代)
解释:将内存分为两块,使用其中一块。垃圾回收时将该块中存活的对象复制到另一块未使用的内存中。清除使用的内存块的所有对象。未使用内存块激活为使用内存块。使用内存块转为未使用内存块。
缺点:存货对象较多时,效率比较差。并且空间折半,内存资源浪费
优点:不会产生内存碎片
5.分代法(Java堆)
解释:根据对象生命周期长短将其分块。再根据每块内存特点,使用不同回收算法,提高回收效率
因此。java虚拟机中的堆根据这种算法分为新生代(复制)和老年代(标记压缩清除)
6.分区算法
解释:将整个空间划分为连续不同的小区间,每个区间独立使用。独立回收
优点:可以一次回收多个小区间。
回收配置
Java提供多种类型的垃圾回收器。JVM中的垃圾收集一般都采用“分代收集”,不同的堆内存区域采用不同的收集算法,主要目的就是为了增加吞吐量或降低停顿时间。
Serial收集器:新生代收集器,使用复制算法,使用一个线程进行GC,串行,其它工作线程暂停。
ParNew收集器:新生代收集器,使用复制算法,Serial收集器的多线程版,用多个线程进行GC,并行,其它工作线程暂停。使用-XX:+UseParNewGC开关来控制使用ParNew+Serial Old收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。
Parallel Scavenge 收集器:吞吐量优先的垃圾回收器,作用在新生代,使用复制算法,关注CPU吞吐量,即运行用户代码的时间/总时间。使用-XX:+UseParallelGC开关控制使用Parallel Scavenge+Serial Old收集器组合回收垃圾。
Serial Old收集器:老年代收集器,单线程收集器,串行,使用标记整理算法,使用单线程进行GC,其它工作线程暂停。
Parallel Old收集器:吞吐量优先的垃圾回收器,作用在老年代,多线程,并行,多线程机制与Parallel Scavenge差不错,使用标记整理算法,在Parallel Old执行时,仍然需要暂停其它线程。
CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。
使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial Old进行内存回收,优先使用ParNew+CMS(原因见Full GC和并发垃圾回收一节),当用户线程内存不足时,采用备用方案Serial Old收集。