一、概述
运行时数据区是jvm运行时的内存布局,类装载到内存后存放的位置,为执行引擎提供所需指令和数据。运行时数据区包括:堆、栈、方法区、本地方法栈、pc计数器。
接下来会详细介绍各个部分,并介绍直接内存访问和方法区中的常量池,另外对于每个区域可能发生的内存异常用demo做讲解。
二、详细介绍各部分
1、堆
分配运行时产生的对象分配在堆中,但是并不是一定就分配在堆中,随着运行时编译器优化技术的不断进步,逃逸分析和标量替换技术的不断成熟,对象也可以分配到栈中。由于jvm内存是由虚拟机自己分配和回收,不需要开发人员手工干预,所以jvm一般会提供gc功能,但是jvm spec并没有明确要求必须提供。
整个系统只有一个堆,所有的对象分配都在一个逻辑区域。堆是一个多线程共享的区域,所以对堆中对象的访问就需要考虑并发。(jvm有什么措施)。
在jvm的实现中,一般堆都会划分成不同的区域,比如hotspot vm的堆划分为年轻代Eden、中生代Survivor和年老代Tenured区域,根据对象存活的年龄放到不同的区域,区域的大小可以通过参数设置。这些讲到gc的时候会详细讲解。
如果要存放的对象超过了堆的最大大小,则会抛出OutOfMemoryError,简称OOM异常。
2、栈
栈主要是运行时存放在方法调用信息(编译器已确定占用空间的东西怎么体现)的区域,栈是线程私有的,每个线程都有一个栈,线程生而栈生,线程死而栈灭。
栈中内存不需要额外收集,每次方法调用完成后都会自动回收。hospot vm中可以通过-Xss设置栈大小,默认是2m。
每次方法调用,都会产生一个栈帧,方法调用结束都会弹出栈帧。栈帧主要包括局部变量表、操作数栈、帧数据区。帧数据区又包括运行时解析常量池会用到的常量池指针、方法返回信息、指向异常的信息(还有什么)
所有栈占用的内存大小怎么计算?虚拟机整个的内存-堆内存-方法区内存-pc计数器-本地方法栈?
如果栈可以动态的在堆中分配,则如果分配不了更多的栈内存的时候会抛出OOM异常,否则会抛出StackOverflowError
3、方法区
方法区存class文件加载到内存后的类型信息,如类型描述符、全限定名、接口、字段、方法、属性信息,还有运行时常量池、指向堆中Class实例和ClassLoader实例的信息。另外还存放jit编译产生的本地代码。
方法区在逻辑上是堆的一部分,但是不同的虚拟机有不同的叫法,hotspot叫做non-heap内存,IBM J9和Jrockit则没有这种叫法。hotspot后续版本也会去掉这种叫法,放到native memory的区域
方法区的类型信息也可以卸载,及gc,但是比较严格,像java动态代理生成的代理类经常会被卸载。类型信息要被卸载必须满足这些条件:a、类型所有实例都被回收了 b、Class对象被回收了 c、加载该类的ClassLoader已经被回收 d、还有什么
该区域也会内存溢出,溢出时候抛出OOM异常,如果jar包太多而方法区内存太小或者运行时动态向常量池添加太多东西,就会抛出OOM
4、本地方法栈
执行本地代码的时候用到的栈,hotspot的本地方法栈和栈是同一块区域。
5、pc计数器
指向当前线程将要指向的下一条指令,线程私有的区域,一个线程有一个pc计数器。该区域不会抛出异常。一个字长
6、Direct Memory
java1.4开始提供了NIO,大量读取数据的时候,不用先把数据拷入jvm内存中,直接从os内存读取。所以该区域不占用jvm的堆内存,通过参数-XX:MaxDirectMemorySize设置该区域大小,如果不设置默认跟堆大小一样大。
该区域会抛出OOM异常,何时?
7、常量池
类文件的常量池装载进内存后同样会形成常量池,这是运行时常量池。与类文件常量池不同的是,运行时常量池在使用的时候需要解析,需要把一些entry的符号引用转化成直接引用。
如3提到,此区域在内存不够用时会抛出OOM。
三、例子
1、堆
先创建一个包含大对象的类
- package com.yymt.jvm;
- public class BigObejct {
- int[] value;
- private static final int M1 = 1024 * 1024;
- public BigObejct() {
- //4 * 1m = 4m
- this.value = new int[M1];
- }
- }
循环创建类,注释中-Xmx10m是启动时虚拟机参数,最大堆内存是10m
- package com.yymt.jvm.oom;
- import com.yymt.jvm.BigObejct;
- public class HeapOOM {
- public static void main(String[] args) {
- BigObejct[] objs = new BigObejct[20];
- for(int i = 0;i < 20;i++){
- objs[i] = new BigObejct();
- }
- }
- }
堆溢出,注意此处OutOfMemoryError: Java heap space表明是堆空间溢出
- Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
- at com.yymt.jvm.BigObejct.<init>(BigObejct.java:8)
- at com.yymt.jvm.oom.HeapOOM.main(HeapOOM.java:15)
2、栈溢出错误StackOverflowError
- package com.yymt.jvm.oom;
- public class StackOverflow {
- public static void main(String[] args) {
- recursiveInvoke(5000, 1, 1);
- }
- public static void recursiveInvoke(int cnt,int param1,int param2){
- if(cnt == 0){
- return;
- }
- int newCnt = cnt - 1;
- int param3 = param1 + param2;
- int param4 = param1 - param2;
- recursiveInvoke(newCnt, param4, param3);
- }
- }
不加-Xss参数时候就直接异常了,至少在我的机子上如此;-Xss100k也可以出异常,-Xss500k就正常运行
- Exception in thread "main" java.lang.StackOverflowError
- at com.yymt.jvm.oom.StackOverflow.recursiveInvoke(StackOverflow.java:13)
- at com.yymt.jvm.oom.StackOverflow.recursiveInvoke(StackOverflow.java:19)
- ......
3、方法区
3.1、如果系统非常庞大,比如说一个很大的产品,如ERP系统、CRM系统,里边有非常多的jar包,运行期这些jar都要加载到内存中,而如果方法区设置的过小,就会导致OOM。在客户分阶段上线过程中,第一阶段只上部分模块,比如erp系统只上财务模块,第二期上供应链,第三期上hr、制造,如果前期MaxPermSize设置的过小,就会导致后期部署完相关模块运行过程中出现方法区溢出。此处因为系统代码庞大,代码无法上传。注意此处是PermGen space
- java.lang.OutOfMemoryError: PermGen space
3.2、如果动态创建无数的类放到内存中同样会出现这个问题
- package com.yymt.jvm.oom;
- import java.lang.reflect.Method;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- import net.sf.cglib.proxy.MethodProxy;
- public class PermGenOOM {
- public static void main(String[] args) throws InterruptedException {
- for (int i = 0; i < 5000000; i++) {
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(DynClz.class);
- enhancer.setUseCache(false);
- enhancer.setCallback(new MethodInterceptor() {
- @Override
- public Object intercept(Object arg0, Method arg1,
- Object[] arg2, MethodProxy arg3) throws Throwable {
- return arg3.invokeSuper(arg0, arg2);
- }
- });
- enhancer.create();
- Thread.sleep(20);
- }
- }
- }
异常:
- Caused by: java.lang.OutOfMemoryError: PermGen space
4、Direct Memory
- package com.yymt.jvm.oom;
- import java.lang.reflect.Field;
- import sun.misc.Unsafe;
- public class DirectMemoryOOM {
- private static final int M1 = 1024 * 1024;
- public static void main(String[] args) throws IllegalArgumentException,
- IllegalAccessException, InterruptedException {
- Field unsafeFld = Unsafe.class.getDeclaredFields()[0];
- unsafeFld.setAccessible(true);
- Unsafe unsafe = (Unsafe) unsafeFld.get(null);
- for (int i = 0; i < 1000000; i++) {
- unsafe.allocateMemory(M1);
- Thread.sleep(2);
- }
- Thread.sleep(100000);
- }
- }
这部分内存的使用情况,目前visualvm工具中是检测不到的
- Exception in thread "main" java.lang.OutOfMemoryError
- at sun.misc.Unsafe.allocateMemory(Native Method)
- at com.yymt.jvm.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:24)