Java堆内存不足错误
Java堆内存不足的原因有可能一下部分:
1)Java堆内存不足的根本原因可能是设置的Heap堆的最大值过小,不足以满足应用的处理要求
2)对象的活动时间过长,如果应用程序中存在很多长时间运行的对象,降低对象的存活的时间。
3)应用程序在内存中缓存对象,cache大小会影响内存
4)内存泄露。例如在连接传回连接池后没有关闭JDBC结果集对象。
5)存在JVM错误,未能执行完成的GC
6)内存碎片
jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默认垃圾收集器G1
-XX:+PrintCommandLineFlagsjvm参数可查看默认设置收集器类型
-XX:+PrintGCDetails亦可通过打印的GC日志的新生代、老年代名称判断
1.Serial收集器 单线程收集器,收集时会暂停所有工作线程(我们将这件事情称之为Stop The World,下称STW),使用复制收集算法,虚拟机运行在Client模式时的默认新生代收集器。
2.ParNew收集器 ParNew 收集器就是Serial的多线程版本,除了使用多条收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial收集器一摸一样。对应的这种收集器是虚拟机运行在Server模式的默认新生代收集器,在单CPU的环境中,ParNew收集器并不会比Serial收集器有更好的效果。
3.Parallel Scavenge收集器 Parallel Scavenge收集器(下称PS收集器)也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。
4.Serial Old收集器 Serial Old是单线程收集器,使用标记-整理算法,是老年代的收集器,上面三种都是使用在新生代收集器。
5.Parallel Old收集器 老年代版本吞吐量优先收集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择,因为PS无法与CMS收集器配合工作。
6.CMS(Concurrent Mark Sweep)收集器 CMS 是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,这一点对于实时或 者高交互性应用(譬如证券交易)来说至关重要,这类应用对于长时间STW一般是不可容忍的。CMS收集器使用的是标记-清除算法,也就是说它在运行期间会 产生空间碎片,所以虚拟机提供了参数开启CMS收集结束后再进行一次内存压缩。
JVM的GC日志解读:
一、 JVM YoungGeneration下MinorGC日志详解
[GC (Allocation Failure) [PSYoungGen:2336K->288K(2560K)] 8274K->6418K(9728K), 0.0112926 secs] [Times:user=0.06 sys=0.00, real=0.01 secs]
PSYoungGen(是新生代类型,新生代日志收集器),2336K表示使用新生代GC前,占用的内存,->288K表示GC后占用的内存,(2560K)代表整个新生代总共大小
8274K(GC前整个JVM Heap对内存的占用)->6418K(MinorGC后内存占用总量)(9728K)(整个堆的大小)0.0112926 secs(Minor GC消耗的时间)] [Times: user=0.06 sys=0.00, real=0.01 secs] 用户空间,内核空间时间的消耗,real整个的消耗
二、 JVM的GC日志Full GC日志每个字段彻底详解
[Full GC (Ergonomics) [PSYoungGen: 984K->425K(2048K)] [ParOldGen:7129K->7129K(7168K)] 8114K->7555K(9216K), [Metaspace:2613K->2613K(1056768K)], 0.1022588 secs] [Times: user=0.56 sys=0.02,real=0.10 secs]
[Full GC (Allocation Failure) [PSYoungGen: 425K->425K(2048K)][ParOldGen: 7129K->7129K(7168K)] 7555K->7555K(9216K), [Metaspace:2613K->2613K(1056768K)], 0.1003696 secs] [Times: user=0.64 sys=0.03,real=0.10 secs]
[Full GC(表明是Full GC) (Ergonomics) [PSYoungGen:FullGC会导致新生代Minor GC产生]984K->425K(2048K)][ParOldGen:(老年代GC)7129K(GC前多大)->7129K(GC后,并没有降低内存占用,因为写的程序不断循环一直有引用)(7168K) (老年代总容量)] 8114K(GC前占用整个Heap空间大小)->7555K (GC后占用整个Heap空间大小) (9216K) (整个Heap大小,JVM堆的大小), [Metaspace: (java6 7是permanentspace,java8改成Metaspace,类相关的一些信息) 2613K->2613K(1056768K) (GC前后基本没变,空间很大)], 0.1022588 secs(GC的耗时,秒为单位)] [Times: user=0.56 sys=0.02, real=0.10 secs](用户空间耗时,内核空间耗时,真正的耗时时间)
三、 Java8中的JVM的MetaSpace
Metaspace的使用C语言实现的,使用的是OS的空间,Native Memory Space可动态的伸缩,可以根据类加载的信息的情况,在进行GC的时候进行调整自身的大小,来延缓下一次GC的到来。
可以设置Metaspace的大小,如果超过最大大小就会OOM,不设置如果把整个操作系统的内存耗尽了出现OOM,一般会设置一个足够大的初始值,安全其间会设置最大值。
永久代发生GC有两种情况,类的所有的实例被GC掉,且class load不存。
对于元数据空间 简化了GC, class load不存在了就需要进行GC。
三种基本的GC算法基石
一、 标记/清除算法
内存中的对象构成一棵树,当有效的内存被耗尽的时候,程序就会停止,做两件事,第一:标记,标记从树根可达的对象(途中水红色),第二:清除(清楚不可达的对象)。标记清除的时候有停止程序运行,如果不停止,此时如果存在新产生的对象,这个对象是树根可达的,但是没有被标记(标记已经完成了),会清除掉。
缺点:递归效率低性能低;释放空间不连续容易导致内存碎片;会停止整个程序运行;
二、 复制算法
把内存分成两块区域:空闲区域和活动区域,第一还是标记(标记谁是可达的对象),标记之后把可达的对象复制到空闲区,将空闲区变成活动区,同时把以前活动区对象1,4清除掉,变成空闲区。
速度快但耗费空间,假定活动区域全部是活动对象,这个时候进行交换的时候就相当于多占用了一倍空间,但是没啥用。
三、 标记整理算法
平衡点
标记谁是活跃对象,整理,会把内存对象整理成一课树一个连续的空间,
JVM垃圾回收分代收集算法
综合了上述算法优略
1, 分代GC在新生代的算法:采用了GC的复制算法,速度快,因为新生代一般是新对象,都是瞬态的用了可能很快被释放的对象。
2, 分代GC在年老代的算法 标记/整理算法,GC后会执行压缩,整理到一个连续的空间,这样就维护着下一次分配对象的指针,下一次对象分配就可以采用碰撞指针技术,将新对象分配在第一个空闲的区域。
JVM垃圾回收器串行、并行、并发垃圾回收器概述
1, JVM中不同的垃圾回收器
2, 串行,并行,并发垃圾回收器(和JVM历史有关系,刚开始串行)
Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。
JVM中Serial收集器、ParNew收集器、Parallel收集器解析
Serial收集器 单线程方式(没有线程切换开销,如果受限物理机器单线程可采用)串行且采用stop the world在工作的时候程序会停止
Serial和serial old
ParNew收集器:多线程(多CPU和多Core的环境中高效),生产环境对低延时要求高的话,就采用ParNew和CMS组合来进行server端的垃圾回收
Parallel 收集器:多线程,并行, 它可以控制JVM吞吐量的大小,吞吐量优先的收集器,一般设置1%,可设置程序暂停的时间,会通过把新生代空间变小,来完成回收,频繁的小规模垃圾回收,会影响程序吞吐量大小
JVM中CMS收集器解密
低延迟进行垃圾回收,在线服务和处理速度要求高的情况下很重要
配置:XX:UseConcMarkSweepGC
concurrence(并发) Mark(标记)Sweep(清理)
低延时
把垃圾回收分成四个阶段
CMS-initial-mark初始标记阶段会stop the world,短暂的暂停程序根据跟对象标记的对象所连接的对象是否可达来标记出哪些可到达
CMS-concurrent-mark并发标记,根据上一次标记的结果确定哪些不可到达,线程并发或交替之行,基本不会出现程序暂停。
CMS-remark再次标记,会出现程序暂停,所有内存那一时刻静止,确保被全部标记,有可能第二阶段之前有可能被标记为垃圾的对象有可能被引用,在此标记确认。
CMS-concurrent-sweep并发清理垃圾,把标记的垃圾清理掉了,没有压缩,有可能产生内存碎片,不连续的内存块,这时候就不能更好的使用内存,可以通过一个参数配置,根据内存的情况执行压缩。
JVM中G1收集器
可以像CMS收集器一样,GC操作与应用的现场一起并发执行
紧凑的空闲内存区域且没有很长的GC停顿时间
需要可预测的GC暂停耗时
不想牺牲太多吞吐量性能
启动后不需要请求更大的Java堆
通过案例瞬间理解JVM中PSYoungGen、ParOldGen、MetaSpace
Heap
PSYoungGen total 2560K, used 321K[0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 2048K, 15% used[0x00000007bfd00000,0x00000007bfd50568,0x00000007bff00000)
from space 512K, 0% used[0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used[0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 7168K, used 7097K[0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000)
object space 7168K, 99%used [0x00000007bf600000,0x00000007bfcee7b8,0x00000007bfd00000)
Metaspace used 2647K, capacity 4486K, committed4864K, reserved 1056768K
class space used 289K, capacity 386K, committed 512K,reserved 1048576K
PSYoungGen是eden + from
使用MAT对Dump文件进行分析实战
导出Dump文件
垃圾收集方法:
1.标记-清除:
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。
2.复制算法:
为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。
于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)
3.标记-整理
该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。
4.分代收集
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。