我们常常做的是将Java内存区域简单的划分为两种:堆内存和栈内存。这种划分比较粗粒度,这种划分是着眼于我们最关注的、与对象内存分配密切相关的两类内存域。其中栈内存指的是虚拟机栈,堆内存指的是java堆。
1.栈内存,即虚拟机栈。每个方法被执行的时候都会同时创建一个栈帧,用来存储局部变量,操作栈,动态链接,方法出口等信息。局部变量包括各种基本类型的变量和对象的引用变量都是在方法的栈内存中分配。其中,64位长度的long和double类型的数据占用2个局部变量的空间,其他数据类型只占用1个。局部变量所需要的内存空间是在编译期间完成的,当进入一个方法时候,这个方法所需的局部变量空间已经确定,在方法运行期间不会改变。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。当线程请求的栈深度大于虚拟机所允许的深度,则抛出StackOverflowError异常。当虚拟机栈无法扩展时候则抛出OutOfMemoryError异常。出现这种情况的解决办法具体参见java调优。
2.堆内存,在虚拟机启动时创建。堆内存的唯一目的就是创建对象实例,所有的对象实例和数组都要在堆上分配。堆是由垃圾回收来负责的,因此也叫做“GC堆”,垃圾回收采用分代算法,堆由此分为新生代和老年代。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。当堆内存因为满了无法扩展时就会抛出java.lang.OutOfMemoryError: Java heap space异常。出现这种情况的解决办法具体参见java调优。
其实,Java内存不只局限于以上两种,还有:
1.程序计数器,(上图中的线程pc寄存器)记录当前线程所执行的字节码的行号。
2.本地方法栈,与上面的虚拟机栈类似,只不过虚拟机栈是为java方法服务,而本地方法栈是用来为native方法服务。
3.方法区,用于存放已被虚拟机加载的类信息、常量、静态变量。垃圾回收在这个区域出现的几率少。在这个区域的回收就是类型的卸载。只有当加载该类型的类加载器实例(非类加载器类型)为unreachable状态时,当前被加载的类型才被卸载。所以一般不会被卸载。
4.运行时常量池,存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放在方法区的运行时常量池中。该区域不会被回收,例如
String s="abc";
s="xyz";
s=null;
"abc"和"xyz"将一直存在于在字符串池中,这就是java内存泄露的原因之一。
-- 当一个字符串没有被引用时,常量池的内容依然会被回收.
转自:http://blog.sina.com.cn/s/blog_6cad92b701019r8o.html#cmt_3817632