一、运行时数据区域
每个区域都有自己的创建和销毁时间
-
程序计数器:当前线程所执行单字节码的行号指示器。是线程私有,即每一个线程内有属于自己的独立程序计数器。不会出现内存溢出现象,因此java虚拟机规范也没有规定其outofmemory的情况。
-
java虚拟机栈:为虚拟机执行的java方法服务。也是线程私有,生命周期与线程相同。
每个方法执行过程都会创建一个栈帧,用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成的过程,都对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放编译期可知的各种数据类型、对象引用和returnaddres类型(指向了一条字节码指令的地址)。
方法在帧中分配的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
两种异常情况:a.如果线程请求的栈深度大于虚拟机所允许的深度,会抛出Stack Overflow异常;b.如果虚拟机栈可以动态扩展(当前大部分的虚拟机允许动态扩展),当扩展无法申请到足够的内存,将抛出OutOfMemory异常。 -
本地方法栈:为虚拟机使用到的native方法服务。作用于虚拟机栈类似。
-Xoss设置本地方法栈大小(无效),-Xss设定虚拟机栈和本地方法栈大小,有效
-
java堆:是虚拟机内存中最大的一块,也是被所有线程共享的一块,在虚拟机启动时创建。作用是存放对象实例,原本是所有的对象实例都在堆上分配内存,但逃逸分析改变了这一点。
堆也负责垃圾收集管理,采用分代收集器,可以分为新生代和老年代。线程共享的堆能划分出多个线程私有的分配缓冲区,可以帮助更快地分配内存以及更好地回收内存。
如果堆中没有很好地完成实例分配,或者无法再扩展时,会抛出OutOfMemory异常。-Xms堆最小值,-Xmx堆最大值,-XX:+heapdumponoutofmemory堆转储快照。
-
方法区:也是线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池:用于存放编译期生成的各种字面量和符号引用,在类加载后进入方法区。
-XX:permsize和-XX:maxpermsize限制方法区大小,从而间接限制常量池的容量,OutOfMemory的信息是permgen space。
CGLib之类的字节码技术,容易产生方法区内存溢出
二、直接内存
jdk1.4引入的基于通道和缓冲区的i/o方式,即NIO,它使用native函数库直接分配堆外内存,然后通过一个存储在java堆中的directbytebuffer对象作为引用进行操作。
避免了数据在java堆和native堆中的来回复制,内存分配也不受java堆的大小限制。但会受到本机总内存的限制,因此在动态扩展时也会出现OutOfMemory的错误。
-XX:maxdirectmemorysize,默认与-Xmx一样,由unsafe实例反射
三、对象的创建
四、对象的布局 对象在内存中的布局分为3块区域:对象头、实例数据、对其填充。
对象头包含两部分:第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志等等;第二部分是类型指针。
实例数据是对象存储的真正有效信息。
对齐填充为非必要,在对象中起到占位符的作用,对象的大小必须是8字节的整数倍。
常见面试题:Stack Overflow和OutOfMemory有什么区别?
内存泄漏(memory leak)和内存溢出(memory overflow)有什么区别?
虚拟机参数应该怎么调?
网友评论