JVM运行时数据区域
1. 程序计数器:一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,为一个在JAVA虚拟机规范中不会抛出OOM情况的区域。
2. JAVA虚拟机栈:描述JAVA方法执行的内存模型,每个方法执行时都会创建一个栈帧用于存放局部变量表,操作数栈,动态链接,方法出口等信息。该区域会出现stackoverflow和outofmemory异常。
3. 本地方法栈:相比较虚拟机栈,本地方法栈为虚拟机使用的native方法服务,也会出现stackoverflow和outofmemory异常。
4. JAVA堆:所有线程共享的内存区域,唯一目的是存放对象实例,同时也是垃圾收集器管理的主要区域,从内存回收角度来看,还可细分为:新生代和老年代;Eden空间,From Survivor和To Survivor空间等。
5. 方法区: 用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码。 俗称为老年代,内存回收主要针对常量池的回收和类型的卸载。
对象的创建:虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过。
对象的内存分配:1. 指针碰撞:内存规整的分为空闲和未分配两大块,把指针向空闲空间那边挪动一段和对象大小相等的距离。2. 空闲列表:虚拟机维护一个未分配的内存空闲列表。
对象的创建非常频繁,存在线程不安全的情况,解决这个问题有两种方法:一种对分配内存空间的动作进行同步处理-实际上虚拟机采用CAS配上失败重传的方式保证更新操作的原子性;另一种是吧内存分配的动作按照线程划分在不同的空间中进行,即每个线程在JAVA堆上预先分配一小块内存,成为本地线程分配缓冲(TLAB)。
对象的访问定位:目前主流有使用句柄和直接指针两种,使用句柄的好处就是reference中存储的是稳定的句柄地址,对象移动时只会改变句柄中的实例数据指针,而reference本身不需要修改;使用直接指针的好处是速度更快,节省一次指针定位的时间开销。
JAVA堆溢出异常时,异常堆栈信息 java.lang.OutOfMemoryError: Java heap space,会进一步提示堆空间
解决:通过内存映像分析工具(eclipse插件安装http://download.eclipse.org/mat/1.6.1/update-site/,或程序http://www.eclipse.org/mat/downloads.php ) 对Dump(备份)出来的堆转储快照分析,确认是内存泄漏(MemoryLeak)还是内存溢出(Memory Overflow)
使用MemoryAnalyzer tool(MAT)分析内存泄漏可参考http://www.blogjava.net/rosen/archive/2010/06/13/323522.html
一些JVM参数
-Xmx (指定JVM堆的最大内存,在JVM启动以后,会分配-Xmx参数指定大小的内存给JVM,但是不一定全部使用,JVM会根据-Xms参数来调节真正用于JVM的内存
-Xms (指定了JVM初始启动以后初始化最小堆内存);例如 -Xmx5m
-XX:+HeapDumpOnoutOfMemoryError(让虚拟机在出现内存溢出异常时备份出当前的内存堆转储快照)
虚拟机栈和本地方法溢出时:线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError
虚拟机在扩展栈时无法申请到足够的内存空间则抛出OutOfMemoryError,两者本质上都是内存太小
-Xss:栈内存大小
JDK1.6方法区和运行时常量池溢出:在OutOfMemoryError后面跟随的提示信息是"PermGen space",(运行时常量池属于方法区 永生代)
Java8 删除了Hotspot JVM中的永生代内存(PermGen,永生代内存主要存储一些需要常驻内存,不会被回收的信息),改为使用本地内存来存储类的元数据信息,并将之称为元空间(Metaspace),也就不会遇到java.lang.OutOfMemoryError:PermGen错误
参数: -XX:PermSize=64m -XX:MaxPermSize=128m 需变成 -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m
本机直接内存溢出:
直接内存可通过-XX:MaxDirectMemorySize,不指定默认和JAVA堆最大值一样;明显的特征是在Heap Dump文件中不会看见明显的异常。