java不像c语言一样,需要手动分配和释放内存,java的内存管理都是由jvm自动处理的,那么是不是我们就不需要了解java的内存管理了呢?不,机器处理并不能保证避免内存泄露和溢出的出现,因此学习好内存管理是我们解决内存泄露和溢出等问题的知识基础。第一步先来学习运行时数据区域。
如下图,这是jvm对管理的内存的一个划分,分为方法区、堆、虚拟机栈、本地方法栈和程序计数器这五个区域,每个区域有着自己的用途,他们创建和被销毁的时间也不同。
runtime_area.png
程序计数器(program counter register)
- 线程私有的一块内存空间,每个线程各有一个程序计数器,互不影响。
- 可以看作当前线程执行的字节码的行号指示器,字节码解释器通过程序计数器来获取下一条需执行的字节码指令,分支、循环、跳转、异常处理、线程恢复都离不开这个计数器
- 若执行java方法,计数器记录字节码指令的地址;若执行native方法,计数器则为空
- 此内存区域是唯一没OutOfMermoryError的区域
虚拟机栈
- 线程私有的,生命周期和线程相同
- 每个方法都会创建一个栈帧来存储局部变量表、操作数栈、动态连接、方法出口等信息。其中局部变量表存放了编译期可知的基本数据类型和对象引用(不是对象)
- 局部变量表在编译期就会完成内存分配,因此在运行期局部变量表的大小时固定的
- 若请求的栈的大小超过虚拟机允许的最大内存,就会抛出StackOverFlowException异常;若虚拟机栈可以动态扩展,扩展时无法申请到足够的内存,会抛出OutOfMermoryError异常
本地方法栈
- 与虚拟机栈类似,有的虚拟机(如 sun Hotspot虚拟机)将本地方法栈和虚拟机栈合二为一
- 本地方法栈为native方法服务,而虚拟机栈为java方法服务
- 会出现的异常和虚拟机栈一样
堆
- 所有线程共享的最大的一块内存。
- 在虚拟机启动时创建,可通过jvm参数 -Xmx和-Xms 来设置堆内存大小
- 几乎所有的对象实例和数组都在堆上存放。逃逸分析这一技术则使得对象不尽都分配在堆上
- 堆进一步划分可分为新生代和老年代,新生代又可分为Eden空间、From Survivor和To Survivor。这些划分都是为了 GC(garbage collection),后面会讲到GC
方法区
- 线程共享
- 存储虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码等数据
- 当方法区无法满足内存分配的需求时,会抛出OutOfMermoryError异常
运行时常量池
- 运行时常量池jdk1.6包括1.6之前位于方法区,1.7以后位于堆中
- class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放于方法区的运行时常量池。
- 具有动态性。也可在运行期中加入运行时常量池,如String的intern方法。
- 内存不够时会抛出OutOfMermoryError异常
直接内存
- 并不是虚拟机规范中定义的区域,但被频繁使用,也可能出现OutOfMermoryError异常
- JDK1.4引入了NIO,引入了一种基于通道和缓冲区的IO方式,可以使用native函数库分配堆外内存,java堆可以通过DirectByteBuffer对象来操作这块内存,提高了性能。
- 受本机内存限制,有时忽略了这块内存,导致各内存区域大于物理内存限制,从而出现OutOfMermoryError异常
网友评论