浅析JVM内存模型

这几天抽空研究了关于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);
	}
}

这里的输出结果为
50
30
50

从内存模型的角度来看,如上所说,基本可以理解为变量在栈,对象在堆。故程序执行到最后时的内存状态可以简单看为:



右侧则是程序建立的引用变量,其实质就是C/C++中的指针变量,这样的话也就可以理解程序中appleA、appleC所指向的是同一个Apple对象。



以上即小生拙见,JMM内容太多,希望大佬们有看到能指出其中的错误。





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值