JVM内存分配与溢出分析

一、内存分配

1、内存区域

年轻代(Young generation)

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

年老代(Tenured / Old Generation)

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

持久代(Perm Area)

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。
年轻代和年老代的划分是对垃圾收集影响比较大的。

这里写图片描述

2、内存分配策略
1)有限分配Eden
-verbose:gc -XX:+PrintGCDetails -XX:+UserSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRadio=8
2)大对象直接分配到老年代
-XX:PretenureSizeThreshold=6M
说明当对象为6M以上时,进入老年代
3)长期存活对象分配到老年代
-XX:MaxTenuringThreshold
默认值为15
4)空间分配担保
在MinorGC之前,会先检查老年代最大可用空间是否可以容纳新生代所有对象(防止新生代全部晋升时放不下),如果可以容纳,则MinorGC可以安全执行。否则,检查是否允许担保失败,是则检查老年代最大可用空间是否大于历次晋升到老年代的对象的平均大小,是则尝试进行MinorGC;小于或者MinorGC失败,则会发起一次FullGC清理老年代。
-XX:+HandlePromotionFailure开启空间分配担保
-XX:-HandlePromotionFailure关闭空间分配担保
5)动态对象年龄判断
为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象直接可以进入老年代,无须等到MaxTenuringThreshold中要求的年龄。下面通过测试代码对allocation2进行注释前和注释后的收集情况进行对比。

二、JVM参数

java启动参数共分为三类
1、标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容。
-verbose:class
输出jvm载入类的相关信息,当jvm报告说找不到类或者类冲突时可此进行诊断。
-verbose:gc
输出每次GC的相关情况。
-verbose:jni

2、非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容;
-Xms512m 设置JVM促使内存为512m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmx512m ,设置JVM最大可用内存为512M。
-Xmn200m:设置年轻代大小为200M。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:
设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-Xloggc:file
与-verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中,文件的位置最好在本地,以避免网络的潜在问题。
若与verbose命令同时出现在命令行中,则以-Xloggc为准。
-Xprof
跟踪正运行的程序,并将跟踪数据在标准输出输出;适合于开发环境调试。

3、非Stable参数(-XX),此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用
性能调优参数:
这里写图片描述
行为参数:
这里写图片描述
调试参数:
这里写图片描述

例如:

-Xmx200m -Xms50m -XX:HeapDumpOnOutofMemoryError -XX:HeapDumpPath=d:/Memory.dump
分配了200M最大空间 ,启动 最小空间50M , 发生了 内存溢出错误 dump路径为Memory.dump
-verbose:gc -XX:+PrintGCDetails
 输出每次GC的相关情况。每次GC时打印详细信息

三、内存区域

这里写图片描述
1、程序计数器
一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器,如果执行java方法,则记录字节码指令的地址,如果为native,则值为undefined,此区域是唯一在JVM中没有规定任何OutofMemoryError情况的区域。
2、java虚拟机栈
描述的是方法执行的动态内存模型,栈帧是当每个方法执行时,都会创建,伴随着方法从创建到执行完成,用于存储局部变量表,操作数栈,动态链接,方法出口等。进栈出栈是以栈帧为单位的。
局部变量表是存放编译器可知的各种基本数据类型、引用类型、returnAddress类型。其内存空间在编译期完成分配,当进入一个方法时,这个方法需要在帧分配多少内存是固定的,在方法运行期间是不会改变局部变量表大小的。
如果只进不出就会出现StackOverFlowError和OutOfMemory的错误
3、本地方法栈
虚拟机栈是为虚拟机执行java方法服务
本地方法栈是为虚拟机执行native方法服务,其余与虚拟机栈相同
4、堆内存
存放对象实例、垃圾收集器主要管理区域
存放新生代、老年代、Eden空间
5、方法区
存储虚拟机加载的类信息(类版本、字段、方法、接口),常量,静态变量,即时编译器编译后的代码等数据
垃圾回收在方法区的行为较少,会存在对常量池的回收和类型的卸载
如果方法区异常,则会抛出OutOfMemory

常量池(字符串常量池、class常量池和运行时常量池):
•在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
•在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。

运行时常量池存放基本类型包装类(包装类不管理浮点型,整形只会管理-128到127)和String(通过String.intern()方法可以强制将String放入常量池)。之所以称之为动态,是因为不单能在编译期产生常量,运行期间也可以,当然运行时常量池同样是所有线程共享。

字符串常量池中的字符串只存在一份。当产生2个相同的字符串时,则常量池只存储一次,堆内存开辟2次空间,使用HashSet的存储方式。

class常量池用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)
•字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
•符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加(String#intern()),符号引用可以被解析为直接引用

四、分析内存的dump文件

使用Eclipse Memory Analyzer

猜你喜欢

转载自blog.csdn.net/xiaoshiyiqie/article/details/80675248