原理
JVM有三种内存,方法区(Method Area)、java栈(java stack)和java堆内存(java heap)。
方法区主要存储常数池、命名常量、static常量等,为线程共享
方法调用,参数,变量地址走java stack,为线程私有
对象内存总是在heap中分配,线程共享。
主要讲的是JVM的堆内存
JVM堆内存分为两块:Permanent Space和Heap Space。
- Permanent 即 持久代(Permanent Generation),主要存放的是Java类定义信息,与垃圾收集器要收集的Java对象关系不大。
- Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation)。年老代和年轻代的划分对垃圾收集影响比较大。
年轻代
所有新生成的对象首先都是放在年轻代。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代一般分3个区,1个Eden区,2个Survivor区(from 和 to)。
大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当一个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当另一个Survivor区也满了的时候,从前一个Survivor区复制过来的并且此时还存活的对象,将可能被复制到年老代。
2个Survivor区是对称的,没有先后关系,所以同一个Survivor区中可能同时存在从Eden区复制过来对象,和从另一个Survivor区复制过来的对象;而复制到年老区的只有从另一个Survivor区过来的对象。而且,因为需要交换的原因,Survivor区至少有一个是空的。特殊的情况下,根据程序需要,Survivor区是可以配置为多个的(多于2个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
针对年轻代的垃圾回收即 Young GC。
年老代
在年轻代中经历了N次(可配置)垃圾回收后仍然存活的对象,就会被复制到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
针对年老代的垃圾回收即 Full GC
老年代和年轻代都是隶属Heap Space。
持久代
用于存放静态类型数据,如 Java Class, Method 等。持久代对垃圾回收没有显著影响。但是有些应用可能动态生成或调用一些Class,例如 Hibernate CGLib 等,在这种时候往往需要设置一个比较大的持久代空间来存放这些运行过程中动态增加的类型。
所以,当一组对象生成时,内存申请过程如下:
- JVM会试图为相关Java对象在年轻代的Eden区中初始化一块内存区域。
- 当Eden区空间足够时,内存申请结束。否则执行下一步。
- JVM试图释放在Eden区中所有不活跃的对象(Young GC)。释放后若Eden空间仍然不足以放入新对象,JVM则试图将部分Eden区中活跃对象放入Survivor区。
- Survivor区被用来作为Eden区及年老代的中间交换区域。当年老代空间足够时,Survivor区中存活了一定次数的对象会被移到年老代。
- 当年老代空间不够时,JVM会在年老代进行完全的垃圾回收(Full GC)。
- Full GC后,若Survivor区及年老代仍然无法存放从Eden区复制过来的对象,则会导致JVM无法在Eden区为新生成的对象申请内存,即出现“Out of Memory”。
OOM(“Out of Memory”)异常一般主要有如下2种原因:
开发时遇到内存溢出时怎么办?
引起内存溢出的常见情况:
- 内存加载的数据良过大,如:从百万~千万级的数据库中一次性获取大量的数据
- 集合类有大量对象的引用,使用完成后未被清空,是的jvm不能回收
- 代码中存在死循环或大循环重复产生对象实体
- 使用的第三方软件或者中间件存在内存溢出bug //情况很少
- 启动参数内存设定的过小 / /针对持久代
前三个主要是年老代的情况,最后一个针对持久代,第三方软件情况很少。
内存溢出的解决方案
- 修改jvm启动参数,直接增加内存(使用-Xms -Xmx)
- 检查错误日志,查看OutOfMemory错误之前是否有其他异常或错误
- 对代码进行走查和分析,找出可能出现内存溢出的错误或地方
- 使用内存查看工具动态查看内存使用情况
- 检查对数据库查询中,是否存在一次性取出全部数据的sql。代之以分页机制
- 检查代码是否存在死循环或者递归调用
- 检查是否有大量产生新对象实体的情况
- 检查List、Map等集合,是否存在使用完后未被清理的情况