JVM(Java Virtual Machine)即Java虚拟机,Java代码都是在JVM上运行的,所以了解JVM是成为Java高手的毕竟之路。
本系列内容将对JVM的知识进行介绍,是从头学习JVM知识的笔记。
本系列内容根据自己的学习和理解的基础上,并参考《深入理解Java虚拟机》一书介绍的知识所写。如果有写的不对的地方,请各位多多提点。
从头开始学习JVM(二)—— Java堆划分及对象的产生
Java堆的划分
一个JVM中只有一个堆内存,其大小是可以调节的。所有的对象实例以及数组都要在堆上分配,所以堆唯一的目的就是存放对象的实例。
早期的堆内存可细分为三个部分:新生区(Young)、老年区(Old)、永久区 (PermGen space)。
JDK6之前堆中存在永久区;
JDK7开始“去永久代”,移除了永久区;
JDK8元数据空间(metaspace)取代了永久区,且元空间存在了本地内存中,即堆中只有新生区与老年区了。
新生区(Young)
新生区又分为伊甸区(Eden)与幸存区(Survivor)。默认情况下年轻代按照8:1:1的比例(SurvivorRadio=8)来分配,可用参数 -XX:SurvivorRadio设置。
- 伊甸区(Eden)
大多数情况下,对象在新生区中的伊甸区中分配。当伊甸区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
大对象直接进入老年区。所谓的大对象是指需要大量连续内存的Java对象,最经典的大对象就是那种很长的字符串以及数组,伊甸区内存不足够存放的对象。
- 幸存区(Survivor)
当经过Minor GC活下来的对象,将进入幸存区。幸存区分为from 和 to 两个区,这两个区是动态的,from 会动态的转换为 to。
幸存区一般使用的是复制算法(后面的GC算法中会讲到)。两个幸存区的作用在于提高性能,避免内存碎片的出现,但是牺牲了内存空间。在任何时候,总会有一个内存区是空的,在发生Minor GC后,对象会移到的幸存区就称为 from 区,to 区总是空的。而下一次Minor GC发生时,新对象和之前 from 区已存在的对象都放入 to 区中,此时 to 就动态变成了 from。
虚拟机中默认定义(MaxTenuringThreshold=15),在幸存区经过15次Minor GC的对象就会移到老年区。
老年区(Old)
老年区一般用于存放长生命周期的对象,通常是从幸存区中拷贝过来的对象。不过大对象是会直接进入老年区的。所谓的大对象是指需要大量连续内存的Java对象,最经典的大对象就是那种很长的字符串以及数组,伊甸区内存不足够存放的对象。
大对象对于虚拟机的内存分配来说是一个“坏消息”,特别是要避免出现一群“朝生夕灭”的“短命大对象”,短命大对象是灾难。经常出现大对象容易导致为了获取足够的连续空间,内存还有不少空间时就提前触发垃圾收集器收集它们。
虚拟机提供一个 -XX:PretenureSizeThreshold参数,当对象空间大于这个设置值时直接在老年区分配,这个参数是为了避免在Eden区和两个Survivor区之间发生大量的内存复制(幸存区使用的复制算法收集内存)。
对象年龄动态判定
为了更好的适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄(即经历的Minor GC次数)必须达到参数MaxTenuringThreshold规定的次数后才能晋升到老年区。如果在幸存区中相同年龄的所有对象的大小总和大于幸存区的一半,那么幸存区中年龄大于等于该对象(指对象的大小总和大于幸存区的一半内存的这个“同龄对象”的年龄)的就会进入老年代。
对象的产生
对象的创建
此处的对象限于普通对象,不包括数组和Class对象等。
Java中,一个对象的创建往往是从new关键字开始,然后再经过init之后才能真正意义上的创建一个Java程序视角可用的对象实例。而Java中,也存在着许多使用new过程的操作,例如克隆、反序列化。
对象首次创建的过程:
- 当一个类被创建(A a=new A();),并且这个类是首次被加载时(去常量池定位到一个符号引用,且检查该引用代表的类没有被加载、解析和初始化过),将Class文件常量池中的变量装入运行时常量池,执行相应的类加载过程,会在堆中开辟出一块内存存放类的class文件(类对象模板)。
- 然后在栈里申请空间,声明变量。接着会在堆中分配一块内存,存储这个类的实例。
- 分配内存之后,将这个类的非静态的成员变量拷贝过来(静态成员不拷贝,所有实例共享)给变量分配内存,会把对象中的值都设为零值,并持有对应的方法区的方法的句柄。
- 然后进行一些必要的设置(例如从对象头中读取类信息,锁状态等)。
- 之后进行类