这几天抽空研究了关于Java虚拟机的内存模型,这部分内容很多,都比较重要,对理解Java过程及面向对象都有很大的帮助。
JVM中的内存可分为多个块,包括栈、堆、静态方法区、程序计数器、final域、Volatile域,最基础的有线程栈、堆和静态方法区。涉及到多线程中编译器和处理器的重排序、锁、顺序一致性等。内容较多, 这里就先从基础块讲起。
JVM内存模型架构图
一、线程栈
栈区为线程私有,当一个方法执行时,每个方法都会建立自己的内存栈,在每个方法内定义的变量(包括局部变量,基本数据类型,返回值,返回地址等)将会逐个放入这块栈内存里,随着方法的执行结束,这个方法中的内存栈也将自然销毁。因此,所有在方法中定义的局部变量都是放在栈内存中。
而栈的大小决定了方法的可用空间和方法调用深度(如递归、嵌套),而理论上JVM的最大线程数量也由可分配给栈的空间及栈大小决定。栈大小可由参数-Xss指定,当然也受JVM版本及操作系统环境影响。
如果请求的栈深度大于最大可用深度,则抛出stackOverflowError;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutofMemoryError。
二、堆
堆则是线程间共享的区域,在虚拟机启动时创建,是JVM管理所管理内存中最大的一块。在程序中创建一个对象时,这个对象将被保存到运行数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收器才会在合适的时候回收它。
而在垃圾回收器执行之前之后发生了什么,这要来看堆内存中的结构。堆内存中可分为两个大区域,一块是Young Generation(新生代),另一块是Old Generation(老生代Old区)。
在新生代中,eden区存放刚创建的新对象,survivorSpace0(S0,from space)和survivorSpace1(S1,to space)中所存放的对象都是至少经历过一次垃圾回收而依然存在的。如果经历了一段时间后还存在,则对象会存入老生代。所以老生代中主要存放应用程序中生命周期长的内存对象。
三、JVM内存分配过程
- JVM 会试图为相关Java对象在eden中初始化一块内存区域。
- 当Eden空间足够时,内存申请结束;否则到下一步。
- JVM 试图释放在Eden中所有不活跃的对象。释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区。
- Survivor区被用来作为Eden及Old的中间交换区域,当Old区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区。
- 当Old区空间不够时,JVM 会在Old区进行完全的垃圾收集。
- 完全垃圾收集后,若Survivor及Old区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory”错误。
四、静态方法区
先说下Static这个关键字,Static是从早先的VB过来的,一开始是VB需要一个不同于局部变量的变量,接着C++延续了Static。而Java也从C++延续了Static这个关键字,然而意义却已经大有不同。
由关键字Static声明的方法或变量称为静态方法或静态变量,也称为类方法或类变量。个人更倾向于后者,因为那些不使用Static修饰的成员则属于该类的某个单例,需要实例化之后才会有相应的栈内存分配给它;而有Static修饰的成员表明该成员属于这个类本身而非该类的某个单例,在JVM加载该类的时候,JVM就会为其分配相应的内存空间,这块内存空间就称为静态方法区。而类方法类变量可由类直接调用,即使没有实例化对象。
至于这里的静态方法区,也叫永生代(Permanent Generation)。如上述,主要存储的就是Java类的类信息,静态变量等,其基本不参与垃圾回收。这里永生代也并不是真的就一直存在,不会被回收,我的意见是类的回收条件:至少是所有该类的实例被回收,而且装载该类的ClassLoader被回收,其次是在没有类的引用的时候,程序会在合适的时候回收内存,具体仍待深究。
五、从内存模型角度看面向对象
public class Apple{
private double price;
public Apple(){}
public Apple(double price){
this.price = price;
}
}
public class TestOO{
public static void main(string args[]){
Apple appleA = new Apple();
appleA.price = 10;
Apple appleB = new Apple(20);
appleB.price = 30;
Apple appleC = appleA;
appleC.price = 50;
System.out.println(appleA.price);
System.out.println(appleB.price);
System.out.println(appleC.price);
}
}