Java虚拟机(2)——内存结构&堆
概述
100%JVM教程或者是书上都会有这张图。。。
啊,,,,这是JVM运行时的内存结构。很多Java基础教程都会提到Java中的堆和栈,但实际上Java的运行时内存结构要更为复杂一些。这里我只介绍堆,剩下的后面再说。
堆
大部分Java学习者都听过类似“栈内存中保存变量的引用,堆内存保存变量的实体”这种说法,其实严格来讲是不正确的,当然为了便于理解,我们现在先这么想,关于特殊情况我们后面再说。
如果对虚拟机栈不了解的可以先看看Java虚拟机(1)——内存结构&栈这篇文章。
由栈到堆
一般来说,栈空间的内存是比较小的,大概也就1M或几M。这只能容纳一些随方法调用而生,随方法结束而消亡的基本数据类型,如果遇到方法中有比较大的局部变量时,栈就放不下了,这时候就需要堆来存储数据。
因为基本数据类型都有明确的空间占用,而且不大,所以这些大数据都是引用数据类型。然后在栈中只需要存储这个大对象的引用,把大对象放到堆中就好了。
就放进去就好了?
当然不是啊,如果JVM只会把这些对象放到堆里而不做任何管控的话,随着程序运行,对象越来越多,我们的堆空间也会耗尽。所以需要一些控制算法,也就是常说的垃圾回收(GC)算法。
IBM的一项研究表明,80%左右的对象都是朝生夕灭的,就是说它们的生命周期特别短,可能方法运行完了,这个对象就用不到了,所以根据这个特性,如果我们把对象分代管理,分成朝生夕灭的
和不动如山的
(生命周期很长的),然后对朝生夕灭的我们经常回收,对那些不动如山的我们很久回收一次,就会缩短垃圾回收操作的时间。
堆空间的分代
堆空间分为新生代和老年代。还有个永久代,那个我们不说,在方法区中说。
新生代对应我们刚刚说的朝生夕灭
的对象,分为Eden
区和S0
、S1
区。
老年代对应刚刚说的生命周期很长的对象。
新生代和老年代的大小比例默认是1:3。堆有600M,则新生代有200M,老年代有400M。而Eden
区又和两个S
区分别以8:1:1的比例分配,也就是说新生代有200M,Eden
区就有160M,S0
、S1
各有20M。
MinorGC和MajorGC和FullGC
绝大多数有分代策略的Java虚拟机GC时都会分成三种,MinorGC是回收新生代中的对象,MajorGC是回收老年代中的对象,FullGC是回收所有包括永久代中的对象。
Stop The World
Java虚拟机中的垃圾回收器是独立出来的线程,我们称为GC线程。但是我们的GC线程进行垃圾回收的时候,并不能真正的做到和用户线程并发(其实有的虚拟机采用一些更先进的技术做到了,但这里不讨论),因为用户线程可能产生新的对象,这会干扰到GC线程对垃圾对象的判断。所以每次GC线程进行垃圾回收,用户线程都会暂停,称为Stop The World。也叫STW。
尽管最先进的虚拟机采用一些先进算法实现了用户线程和GC线程的并发,实际上还是有一些恢复操作和标记操作需要短暂的STW,只是不明显。想深入了解的可以去看看《深入理解Java虚拟机》。
Eden区
预备知识都做完了,往下学。
Eden区翻译过来是伊甸园区,所有Java对象在new的时候都会先尝试进如Eden区,如果空间足够则直接进入,如果空间不够了,会先触发一次MinorGC,MinorGC会把Eden区没有引用的对象清理掉,把其余的对象放入S0或S1区。
S0和S1
全称是Survivor0和Survivor1。翻译过来就是幸存者区。也叫From和To区。S0和S1只有一个分区有数据,这个有数据的分区叫做From区,无数据的就是To区。这两个分区会不停的转换。From变成To,To变成From。
MinorGC时除了把Eden区无用对象清理掉外,还会把幸存者区的无用对象清理掉,Eden区中进入幸存者区的对象会得到一个计数器,代表该对象在幸存者区中熬过了几次MinorGC。每次MinorGC时都会将From分区的数据和从Eden区过来的数据都放到To区。
当对象在幸存者区熬过了15次MinorGC(默认,可设置)则会晋升到老年代中。代表该对象八成是一个生命周期很长的对象。
前面说了幸存者区乃至整个新生代的空间都不大,那如果幸存者区剩余空间接不住Eden区过来的对象咋办?这个对象会直接进入老年代。
老年代
每次老年代满了,都是因为新生代的数据过来了,因为老年代的数据量很大,而且可能有大对象,STW的时间会很长,所以MajorGC不会轻易触发。老年代满了会先触发一次MinorGC,如果释放新生代空间后新生代的新数据能装下了,就不调用MajorGC,否则才调用MajorGC。
当MajorGC后老年代还是装不下新来的对象,这时候就会抛出OOMError
了。
对象一定在堆空间中嘛?
随着虚拟机越来越聪明,很多对象不一定要分配到堆中了。实际上有的对象确实没必要,只会增加GC的压力。每次GC不管是什么GC始终都要有STW,不管时间长短都是要有的,所以人们想了很多办法不让对象分配到堆中。
具体的关键词有栈上分配、逃逸分析、标量替换等,就不说了。