谈谈我对Java内存模型的理解

内存模型与运行时数据区

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分成若干不同的数据区域。
Java内存模型的主要目的是定义程序中各个变量的访问规则,在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
这里所讲述的主内存,工作内存与Java内存区域中Java堆、栈、方法区等并不是同一层次的内存划分,这两者基本上是没关系的。如果两者一定要勉强对应起来,从变量、主内存、工作内存的定义来看,主内存主要是对应Java堆中的对象实例数据部分,而工作内存则是对应了虚拟机栈中的部分内存区域。
(重点存储数据的是堆和方法区(非堆),内存的设计也着重从这两方面展开,这两块区域都是线程共享的,对于虚拟机栈、本地方法栈,程序计数器都是线程私有的)。
关于运行时数据区相关内容在我的上一篇文章中有讲到
在这里插入图片描述

根据之前对于Heap的介绍可以知道,一般对象和数组的创建会在堆中分配内存空间,关键是堆中有这么多区域,那一个对象的创建到底在哪个区域呢?

对象创建所在区域

一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大对象会直接分配到Old区。

假如有对象A、B、C等创建在Eden区,但是Eden区的内存空间是有限的,假如是100M,已经使用了100M或者到达一个设定的临界值,这时候就需要对Eden区内存空间进行清理,也就是垃圾回收操作(Garbage Collect),称为Minor GC,也就是Young区的GC。

经过Minor GC后,有些对象会被清理掉,有些对象还存活着,对于存活的对象需要将其复制到Survivor区,然后在清空整个Eden区域。

Survivor区是个啥?

Survivor区分为两块,S0和S1,也可以叫From 和 To。再同一个时间点上,S0和S1只能有一个区有数据,另一个是空的。

接着上面的GC说,比如一开始只有Eden区和From区中有对象,To是空的。
此时进行一次GC操作,From区中的对象的分代年龄会+1,Eden区中所有存的活对象都会被复制到To区,From区中的还存活的对象会有两个去处。
1.对象的分代年龄达到之前设置好的年龄阈值,会被移动Old区
2.没有达到年龄阈值会被复制到To区
此时Eden区和From区已经被清空了(被GC的对象肯定没了,没有被GC的对象该去哪去哪了)
这时候From成了To,To成了From,From和To角色互换。 也就是说无论如何都要保证名为To的Survivor区是空的。
Minor GC 会一直重复这个过程,直到To区被填满了, 会将所有的对象复制到Old区。

Old区

一般Old区都是年龄比较大的对象,或者相对超过了某个阈值的对象
在Old区也会有GC的操作,Old区的GC称为Major GC,每次GC后还能存活的对象年龄也会+1,如果年龄超过了某个阈值,就会被回收。

对象的生命周期

我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。 有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时 候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上 闯闯了。 于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年 (每次GC加一岁),然后被回收

常见问题 (欢迎补充)

GC都有啥呀?
  • Minor GC 新生代
  • Major GC 老年代
  • Full GC 新生代 + 老年代
为什么需要Survivor区,只有Eden区不行吗?

如果没有Survivor,Eden区每一次Minor GC,存活的对象就会被送到Old区。Old区很快会被填满,从而触发Major GC(Major GC一般会伴随着Minor GC,可以看成是Full GC),Old区的内存空间大于新生代,进行一次Full GC消耗的时间比MInor GC长得多。

那是不是对Old区的空间进行增加或者减少就可以了?
假如增加了Old区的空间, 更多的存活对象才能填满Old区,虽然可以降低Full GC的频率,但是随着Old区空间的增大,一旦发生Full GC ,执行所需要的时间就会更长。
所有Survivor的存在意义,就是减少被送到Old区的对象, 进而减少Full GC的发生,Survivor的预筛保证,只有经历16的Minor GC还能在新生代中存活的对象,才会被送到Old区。

为什么需要两个Survivor区?

最大的好处就是解决了碎片化。
假设一下只有一个Survivor区:
刚刚新建的对象在Eden区,一但Eden满了,触发一次MInor GC , Eden区中存活对象就会被移动到Survivor区,这样继续循环下去,下一次Eden满了的时候(问题点),此时进行MInor GC , Eden和Survivor各自都有一些存活的对象, 如果此时把Eden区的存活对象硬方到Survivor区,很明显这两部分对象所占用的内存不是连续的,也就导致了内存的碎片化。
永远有一个Survivor区是空的,另一个非空的Survivor区无碎片。

新生代中Eden : S0 : S1 为什么是 8:1:1

新生代中的可用内存: 复制算法用来担保的内存为 9:1
可用内存中的Eden:S0区为8:1
新生代中Eden:S0:S1 = 8:1:1

P70 复制算法

现代的商业虚拟机都采用这种收集算法来回收新生代,IBM公司专门研究表明,新生代额对象大概98%是“朝生夕死,九死一生”的,所以并不需要按照1:1的比例来划分内存空间,而是将新生代内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor,当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另一块Survivor空间上,最会清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例就是8:1,也就是每次新生代中可用的内存空间为整个新生代容器的90%,如果新生代经过回收后,存活的对象超过10%,这样就导致另一块Survivor空间不足以存放存活对象,此时,这些对象直接通过分配担保机制进入老年代。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章