转自https://www.cnblogs.com/wuqinglong/p/9305799.html,感谢作者的无私分享。
概述
内存是程序运行的地方,使用Java语言编程代码时,适当的了解Java语言对于内存的划分及使用情况是有必要的。
Java虚拟机定了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机的启动而创建 ,随着虚拟机的退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。
内存结构(面积不代表占用内存的大小)
线程私有:PC寄存器、虚拟机栈、本地方法栈
线程共享:堆、方法区
各区域的作用
堆(Heap)
描述
在Java虚拟机中,堆(Heap)是可供各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。
Java堆在虚拟机启动的时候就被创建,它存储了被自动内存管理系统(也就是场所的Grabage Collector(垃圾收集器))所管理的各种对象,这些受管理的对象无需也无法显示的销毁。Java堆得容量可以是固定的,也可以是随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。Java堆所使用的内存不需要保证是连续的。
如果实际所需的堆超过了自动内存管理系统所能提供的最大容量,那么虚拟机将会抛出一个OutofMemoryError异常。
方法区(Method Area)
在Java虚拟机中,方法区是可供各个线程共享的运行时内存区域。方法区与传统语言中的编译代码存储区或者操作系统进程的正文段的作用非常类似,它存储了每一个类的结构信息,例如,运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容,还包括一些在类、实例、借口初始化时用到的特殊方法(关于特殊方法了解即可)
方法区在虚拟机启动的时候创建,虽然方法区是对的逻辑组成部分,但是简单的虚拟机实现可以选择在这里不实现垃圾收集和压缩。这个版本的Java虚拟机规范也不限定方法区的内存位置和编译代码的管理策略。方法区的容量可以是固定的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。方法区在实际内存空间中可以是不连续的。
如果方法区的内存空间不能满足内存分配需求,那么Java虚拟机将跑出一个OutofMemoryError异常。
运行时常量池(Runtime Constant Pool)
它是class文件中每一个类或接口的常量池表(constant_pool table)的运行时表示形式,它包括了若干种不同的常量,从编译期可知的数值字面常量到必须在运行解析后才能或得的方法或字段引用。运行时常量池类似于传统语言中的符号表,不过它存储数据的范围比通常意义上的符号表要更广泛。
每一个运行时常量池都在Java虚拟机的方法区中分配,在加载类和接口到虚拟机后,就创建对应的运行时常量池。
在创建类和接口时,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那么Java虚拟机将会抛出一个OutofMemoryError异常。
PC寄存器(Program Counter Register)
Java虚拟机可以支持多条线程同时执行,每一条Java虚拟机线程都有自己的PC寄存器。在任意时刻,一条Java虚拟机线程只会执行一个方法的代码,这个正在被线程执行的方法叫当前方法。
如果这个方法不是native的,那PC寄存器就保存Java虚拟机正在执行的字节码指令的地址,如果该方法是native的,那么PC寄存器的值是undefined。PC寄存器的容量至少应当能保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令,因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
虚拟机栈
每一条Java虚拟机都有自己私有的Java虚拟机栈,这个栈与线程同时创建,用于存储栈帧(Frame)。Java虚拟机栈的作用于传统语言中的栈非常相似,用于存储局部变量于一些尚未算好的结果。另外,它在方法调用和返回中也扮演了很重要的角色。因为除了栈帧的出栈和入栈之外,Java虚拟机栈不会再受其它因素的影响,所以栈帧可以在堆(区分开Stack、Heap 和 Java(VM) Stack、Java Heap)中分配,Java虚拟机栈所使用的内存不需要保证是连续的。
本地方法栈
Java虚拟机实现可能会使用到传统的栈(通常称为C Stack) 来支持native方法(指使用Java以外的其它语言编写的方法)的执行,这个栈就是本地方法栈。当Java虚拟机使用其它语言来显示指令集解释器时,也可以使用本地方法栈。如果Java虚拟机不支持native方法,或是本身不依赖传统栈,那么可以不提供本地方法栈,如果支持本地方法栈,那么这个栈一般会在线程创建的时候按线程分配。
如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError异常。
如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个OutofMemoryError异常。