JVM内存模型,
学习和使用JAVA的过程中,各种各样和内存有关的问题始终困扰着人们,内存泄露,并发访问,堆空间溢出等等。
在多线程盛行的今天,JVM内存模型(JMM)更是成为了一个不可避免的课题摆在大家面前,只有理解了这个模型,才能理解在什么情况下会发生什么事情,否则遇到问题你只能手足无措。
先说说什么叫JVM内存模型,JVM启动时,操作系统会分配给JVM一块内存,这块内存有多大可以在启动参数中配置,然后的事就交给JVM去做了,你用 JAVA语言,就要从这块内存中请求空间,你要请求空间,就要以JVM给你的方式来做,所以这不是操作系统为你提供的,而是JVM为你提供的,一种使用内存的方式,也就是说,JVM为你创造了一个执行环境,我们只要用JAVA(更准确的说,只要你的语言可以用JVM执行),就是在这种环境下工作,这就实现了平台无关性。
那么这是一个怎样的环境呢?首先,JVM把这块内存分为两块(其实不止两块,还有方法区,常数池等,只是这两块比较重要),一块叫栈,一块叫堆,不要把这两个名字和数据结构中的堆栈联系起来(其实栈是一样的,但是堆就不太一样了,数据结构里的堆实际是一颗二叉树),因为这只是 JAVA起的两个名字而已,栈主要用于存储变量的引用(我是这么理解的),就是一些指向堆内存地址的指针,而堆主要是用来在new的时候动态分配空间。也就是说你的程序一启动,声明了一个变量的时候,这个变量的指针是保存是在栈里的,而这个变量的值所创建的内存空间是在堆中创建的。
栈能用的内存是固定大小的,可以用-oss来指定栈的大小,而堆的大小是可以随着程序运行改变的,可以用-ms和-mx指定更大和更小值。
然后JVM把堆分为三个区,分别是新成员区,老成员区和区,当一个对象刚刚被创建,那么它就是在新成员区里,过了一段时间,就会被放到老成员区,而在区的则是永远也无法被垃圾回收的对象,我认为之所以这么分类完全是为了垃圾回收的方便,不用去遍历所有内存,重点遍历新成员区,偶尔遍历老成员区,而区根本就不用回收了,那种static final的对象应该就会放在区吧,因为永远都无法改变他们的值了。
这样我们的脑海里面就应该有个模型了,程序启动,栈里面就会放入大量的指针,堆里面就会放入很多对象,每个程序都会伴随着一系列的出栈入栈,垃圾回收器定时检测堆里的内容,如果已经没有被栈所引用了,就将这块内存标为可用了(关于垃圾回收算法,我这里就不详细描述了,上面说的把堆分区什么的,都是垃圾回收策略,还有紧凑什么的,我觉得是比较复杂的,理解就可以了)。
每次方法调用,都伴随着一系列的出栈入栈,堆里的对象是可重用的,有时候会有很多个指针指到一个对象,但是栈里的指针是伴随着方法的开始而诞生,方法的结束而消亡,每次调用都是独立的。
假如方法是同步的,或者方法内有一块是同步的,那么意味着这个被同步的对象(也就是我们的锁),只能同时在栈中被引用一次(也许可以这样理解),于是JVM 就依靠在这个对象上进行计数(不能超过1)来进行同步控制,那么信号量呢,就是这个数字可以由程序员设置,而所有这一切都是要在jvm存储模型上去实现的。