内存结构
java内存结构主要有三大区块:栈内存,堆内存,堆外内存(直接内存)。其中:
1.栈内存主要是存放线程的栈信息,每个线程有独立的线程栈。
2.堆内存有元信息区和对象区,对象区则采用分代的策略,便于垃圾回收。
3.元信息区主要存放类信息,静态变量信息。
4.此外还有堆外内存,这部分内存不受垃圾回收器的管理,其内存管理自成另一套体系。
所有的内存空间之和不能超过物理内存,以及操作系统规定的内存限制。
栈内存
每个线程拥有独立的线程栈。占中存放线程的调用信息,对象引用,以及基本类型的对象(大小确定的,如int ,short, long, byte, float, double,boolean,char)。
可以通过-Xss 指定每个线程栈的大小,默认会使用jdk自带的大小。
当递归层数过多,或者线程的对象应用过多,可能造成栈内存不足的报错:StackOverFlowError.
堆内存
存放通过new 创建的对象实例。所有线程共享。运行时动态分配内存。jvm会监控对象实例的引用状态等,并采用垃圾回收机制,尽可能优化堆内存的使用,释放无用的对象实例。
垃圾回收
jvm通过垃圾回收来回收堆和方法区中的内存,基本原理就是找到城中不再被使用的对象,然后回收掉这些对象占用的内存。自动回收机制,以使得程序员摆脱内存管理的繁杂事务。垃圾回收有可达性检测,整理无用对象实例,以及分代/分区思想等内容。
可达性检测
即检测对象是否不再被使用。主要有引用计数和跟踪计数两种。
引用计数器记录对象是否被引用,当计数器为0时,说明对象已经不再被使用,可以回收。但是java中的引用关系非常复杂,存在循环引用的情况,因此引用计数器不适合。
跟踪计数:执行的时候,从根集合开始扫描对象的引用关系,从节点向下搜索,如果一个对象到根集合没有引用链,那么该对象是不可用的。根集合主要值:虚拟机栈中引用的对象、方法区中类静态属性引用的休想、方法区中敞亮引用的对象、本地方法栈中jni引用的对象。
整理策略
- 复制:从根集合中扫描出存活的对象,然后将存活的对象复制到一块新的未使用空间。当存活对象较少时,比较高效。
- 标记清除:从根集合开始扫描,标记存活的对象。再扫描整个空间未标价的对象,然后回收,对象无需移动,高效但是有碎片。
- 标记压缩:标记形式与“标记清除”一样,但是回收不存在的对象后,会做一次内存整理。成本高但是无碎片。
分代、分区思想
分代和分区思想,将堆内存区分管理,对不同的区块进行不同策略的回收机制,使得生命周期短的对象能够快速被收回,生命周期长的对象能够在适当的时候做统一回收。
年轻代,年老代的设置即时分代和分区思想的一种实现。
垃圾回收器
串行收集器(Serial):指的是使用一个单线程进行垃圾收集,工作的时候会暂停其他工作线程。
并行收集器(Parallel):使用多线程完成垃圾收集,工作的时候会暂停其他工作线程。
CMS收集器:并发标记清除,少量时间会暂停其他工作线程,其余时间与工作线程共同执行。
G1收集器:垃圾优先收集器,设计初衷是尽量缩短超大堆(大于4G)时产生的停顿,相对于cms来说内存碎片大大降低,但是也会存在可能会造成停顿时间较长。jdk 1.7u4以上可以使用。不过目前线上还没有使用这种策略。
堆外内存
将对象分配到java虚拟机以外的内存,这部分内存直接受操作系统管理,可以减少受到垃圾回收对应用程序造成的影响。使用java.nio.DirectByteBuffer对象进行堆外内存的管理和使用。
为什么要有堆外内存
减少垃圾回收;加快复制到远程的速度:堆内存在flush到远程时,会先复制到直接内存,然后再发送,而堆外内存少了复制这一步。
堆外内存的问题
内存难以控制
gc触发时机的问题
何时触发minor gc
- 当eden区分配内存,发现空间不足,jvm就会触发minor gc
- 程序中调用Sysetm.gc()
何时触发cms gc
- 老年代或者持久代已经使用的空间达到设定的百分比时(CMSInitiatingOccupancyFraction这个设置old区,perm区也可以设置);
- JVM自动触发(JVM的动态策略,也就是悲观策略)(基于之前GC的频率以及旧生代的增长趋势来评估决定什么时候开始执行),如果不希望JVM自行决定,可以通过-XX:UseCMSInitiatingOccupancyOnly=true来禁用
何时触发full gc
- 老年代空间不足:java.lang.OutOfMemoryError: java heap space
- Perm空间不足:java.lang.OutOfMemoryError: PermGen space
- cms gc 时出现 promotion failed 和 concurrent mode failure(cms正在进行,但是老年代内存不足,需要尽快回收老年代的对象)
- 统计得到的minor gc 晋升到老年代的平均大小大于老年代的剩余空间
- 主动触发 full gc : 执行 jmap -histo:live [pid]
关于内存管理的jvm 参数设置
大小
- -Xms5g 最小堆内存
- -Xmx5g 最大堆内存,建议与最小堆内存一致,避免jvm动态调整
- -Xmn2g 新生代内存
- -XX:MetaspaceSize=512m jdk8 中元信息的空间大小
- -XX:MaxMetaspaceSize=512m jdk8 中元信息的最大空间大小
- -XX:MaxDirectMemorySize=1g 直接内存的最大空间,超过空间,会执行一次内存回收。如果不设置,会和-Xmx基本差不多,导致直接内存和堆内存相加超过物理内存,报内存空间不足的错误。
- -XX:SurvivorRatio=8 eden和suvivor的比例。例如设置为8,则eden:s0:s1的比例为8:1:1
垃圾回收器策略
- -XX:+UseConcMarkSweepGC 使用cms(并发标记消除)进行老年代的回收,默认新生代采用ParNew回收
- -XX:+UseParallelGC 使用并行
- -XX:+UseSerialGC 串行gc
其他
cms相关的参数
- -XX:CMSInitiatingOccupancyFraction=75 内存使用率达到75%后,开始cms收集
- -XX:+UseCMSInitiatingOccupancyOnly 禁用jvm cms的悲观策略,仅根据内存使用率来触发cms
- -XX:+UseCMSCompactAtFullCollection cms是消除策略,有内存碎片。指定该参数后,在fgc后会整理碎片。
- -XX:CMSMaxAbortablePrecleanTime =5000
- -XX:+CMSClassUnloadingEnabled 方法区使用cms,允许对类的元数据进行回收
- -XX:CMSPermGenSweepingEnabled 方法区使用cms
gc日志相关参数
- -Xloggc=/gc.log gc日志输出的地址
- -XX:+PrintGCDetails 开启详细gc模式
- -XX:+PrintGCDateStamps 每一行前面加上绝对时间戳
- -XX:+HeapDumpOnOutOfMemoryError 内存溢出时输出堆日志
- -XX:HeapDumpPath=/heap.hprof 堆日志输出地址
参考资料:
[1]【原】java内存区域理解-初步了解 http://iamzhongyong.iteye.com/blog/1333100
[2] Java HotSpot VM Options http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html#Options