前言
上一节我们详细学习了类加载器,我们接着思考下一个问题:
类加载器将class文件加载到JVM内存的什么位置?JVM内部内存是怎样划分的?
通过本节我们将寻找到答案,并将掌握以下知识:
1. JVM虚拟机3大组成部分
2. 内存模型
3. 栈、堆详细解释
干货满满,我坚持写,你加油看!!!
JVM虚拟机的内存划分
JVM虚拟机由3大部分组成:类装载子系统、运行时数据区(内存模型)、字节码执行引擎。这3部分是怎样配合工作的呢?
类装载子系统将class文件加载到运行时数据区。字节码执行引擎去执行代码
我们主要研究的是运行时数据区,也叫做内存模型。内存模型内又主要划分成了5块内存区域:栈、堆、本地方法栈、方法区(元空间)、程序计数器。下面我们详细研究这5块内存区域。
栈
栈,又称为线程栈。在一个线程创建时,就会在栈空间内分配一块本线程私有的内存空间,叫做线程栈。每个线程的线程栈相互隔离,互不干扰。
在线程执行某个方法时,会在自己的线程栈内给这个方法分配一块内存,叫做栈帧。主要用来存放局部变量。方法执行完毕之后,就会释放栈帧的内存。
栈帧内部又主要划分成4块内存区域:局部变量表、操作数栈、动态链接、方法出口。
局部变量表:存放这个方法内的局部变量。操作数栈:在运行期间,需要操作的操作数临时中转内存空间。动态连接:将符号引用转换为直接引用。方法出口:方法调用完成,去哪里继续执行。
方法区
方法区,主要存放3类信息:常量、静态变量、类信息。
方法区和永久代以及元空间有什么关系?
方法区、永久代以及元空间的关系类似于Java种的接口和类的关系。类可以看作是永久代和元空间,接口可以看作是方法区。也可以说永久代以及元空间是hotSpot虚拟机对虚拟机规范中方法区的两种实现。
永久代是1.8之前的方法区实现,元空间是1.8之后的元空间实现。
为什么要将永久代替换为元空间呢?
整个永久代有一个JVM本身设置的固定大小上限,无法进行调整。而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍然可能溢出,但相对永久代几率更低。
程序计数器
程序计数器,是用来记录程序当前执行的位置。每个线程都有自己独有的程序计数器。
当User.class文件被加载到方法区后,由字节码执行引擎去执行代码,每执行完一句代码,字节码执行引擎都要去修改程序计数器中的代码位置。
本地方法栈
C++语言编写的,native方法。每个线程独有的。
堆
堆内存空间划分
堆内存分为年轻代和老年代,年轻代占堆内存的1/3,老年代占堆内存的2/3。年轻代内存又分为Eden区和Survivor区,Eden区占年轻代的8/10,Survivor区占年轻代的2/10。Survivor区又分为S0、S1,各占Survivor的一半空间。
为什么区分年轻代和老年代?
这主要和垃圾收集有关。我们创建的对象,它们的存活时间是不同的,为了方便收集垃圾对象,将堆内存划分为年轻代和老年代。
年轻代里的对象,创建之后很快就会被回收,需要一种垃圾回收算法。
老年代里的对象,需要长期存在,所以需要另外一种回收算法。所以需要两个区域来存放不同的对象。
对象在年轻代和老年代间的流转
我们应该从以下几个问题入手,去分析对象在年轻代和老年代间的流转。
1. 哪些对象进入年轻代?哪些对象进入老年代?
2. 年轻代什么时候触发Minor GC?
3. 老年代什么时候触发Full GC?
1.哪些对象进入年轻代?哪些对象进入老年代?
我们创建的大部分对象都会优先在年轻代分配内存,大对象会直接进入老年代。达到分代年龄的对象,从年轻代转移到老年代。
2.年轻代什么时候会触发Minor GC?
当Eden区沾满后,就会触发Minor GC.