运行时数据区:虚拟机把程序运行时所管理的内存区域分为不同的数据区,每个数据区都有自己的用途,创建和销毁时间,其中线程私有的有Java虚拟机栈,本地方法栈,程序计数器,共享的有方法区和堆。
程序计数器:程序计数器是一块很小的内存区域,它可以看做是虚拟机执行指令的行号指示器,字节码解释器就是通过改变程序计数器的值来选取下一条指令,分支,循环,跳转,线程恢复等操作都需要借助程序计数器来执行。它是Java虚拟机规范中唯一没有规定内存溢出情况的区域。在执行Java方法时,程序计数器记录虚拟机正在执行的字节码指令地址,在执行本地方法时,程序计数器值为undefined。
Java虚拟机栈:Java虚拟机栈就是解释Java方法的内存模型,在一个线程被创建时,会为它分配一块栈空间,线程被销毁时,栈空间会被回收,栈与线程拥有相同的生命周期,栈中元素为虚拟机进行方法调用提供支持,每执行一个方法都会创建一个栈帧来存储方法局部变量表,方法出口,操作栈和动态链接,每个方法从调用到执行完毕,就是一个栈帧从入栈到出栈的过程。异常 1.线程所申请的栈深度大于虚拟机所允许的最大深度抛出*Error 2.如果栈空间深度可以动态扩容,但没有足够的内存空间会抛出OutOFMemoryError
本地方法栈:本地方法栈和Java虚拟机栈很类似,不同的是一个为虚拟机执行Java方法提供服务,一个为本地方法提供服务,在执行本地方法时虚拟机栈保持不变,动态链接并直接执行本地方法。
堆:堆是Java虚拟机管理的内存空间中最大的一块空间,被所有线程共享,在虚拟机启动时就进行创建,堆用来存放对象实例,Java中几乎所有的对象实例都会存储在堆上,堆可以在不连续的内存空间中进行存储,逻辑上应该连续,但是类似数组这样的大对象,大部分虚拟机也会要求存储在连续的内存空间中,堆既可以被实现为固定大小也可以被实现为可扩展,可通过-Xms和-Xmx来指定堆的最小和最大空间,当前主流的JVM都可以实现可扩展,如果堆没有空间实现对象实例的分配和无法扩展堆内存抛出OutOfMemoryError
方法区的作用:方法区用来存储被虚拟机加载的类型信息,常量,静态变量和编译器编译后的代码缓存等数据,JDK8以前使用永久代来实现方法区这容易造成内存溢出,在JDK7中逐渐去永久代把字符串常量池,静态变量移出方法区,存储在堆中,在JDK8中废弃了永久代,把类型信息,常量、字段保存在元空间内,静态变量和字符串常量池仍然在堆中,元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存。虚拟机规范对方法区的约束较为宽松,除了和堆一样不需要连续内存和可固定大小或可扩展外,还可以不进行垃圾回收,垃圾回收在方法区中较少出现,主要针对类型卸载和常量池,如果方法区无法满足内存分配要求抛出OOM
运行时常量池:运行时常量池是方法区的一部分,Class文件中除了类的版本,方法,字段、接口等描述信息外,还有一个信息是常量池,用于存放编译期产生的各种字面量和符号引用,这部分内容会在类加载后加载进入运行时常量池,Java语言中并不要求常量一定在编译期产生,也就是并非只有进入Class常量池的常量才能进入运行时常量池,运行时也可能把字符串放入常量池中,这种特性被用在String类中的intern()方法中。
intern()方法会查找在运行时常量池中是否包含一份equals相等的字符串,如果没有把字符串加入到常量池中,如果有则返回该字符串的引用。
直接内存:直接内存不属于运行时数据区,也不是虚拟机定义的内存区域,但是这部分内存被频繁调用,而且可能发生内存溢出。JDK1.4 中新加入了 NIO 这种基于通道与缓冲区的 IO,它可以使用 Native 函数库直接分配堆外内存,通过一个堆里的 DirectByteBuffer 对象作为内存的引用进行操作,避免了在 Java 堆和 Native堆来回复制数据。直接内存的分配不受 Java 堆大小的限制,但还是会受到本机总内存及处理器寻址空间限制,一般配置虚拟机参数时会根据实际内存设置 -Xmx
等参数信息,但经常忽略直接内存,使内存区域总和大于物理内存限制,导致动态扩展时出现 OOM。
内存溢出和内存泄漏,溢出是程序所申请的内存空间大于实际内存空间,泄漏是指程序无法释放已申请的内存空间最后也会导致内存溢出。