前言
在之前我们知道,Java源文件通过JavaC编译器编译成了字节码文件(.class文件),接下来我们就要让Java虚拟机运行字节码文件,从而得到我们最终想要的结果。在这个过程,Java虚拟机会载入字节码文件,将其存入Java虚拟机的内存空间中。
那么字节码数据在Java虚拟机中是如何存放的呢?Java虚拟机在为类实例、成员变量分配内存是如何分配的呢?带着这些问题,我们先来了解一下Java虚拟机的内存结构。
Java虚拟机内存结构(Java虚拟机规范中也称之为运行时数据区)
Java虚拟机内存结构可以分为线程公有和线程私有的两部分:
- 公有部分
公有部分指的是所有线程都共享的部分,包含了Java堆、方法区、常量池。 - 私有部分
私有部分指的是每个线程私有的数据,包含了PC寄存器、Java虚拟机栈、本地方法栈。
1、公有部分:Java堆、方法区、常量池
- Java堆是从JVM划分出来的一块区域,这块区域专门用于Java实例对象的分配,几乎所有的对象都会在这里进行内存分配,也就是说几乎所有的实例对象都存储在Java堆中。(有些时候小对象会直接在栈上进行分配,暂不介绍)
-
方法区是存储Java类字节码数据的一块区域,它存储了每一个类的结构信息,比如运行时常量池、字段、方法数据、构造方法等。这里我们知道常量池是存储在方法区中的。
Java堆我们知道是用来存储实例对象的,并且Java堆根据对象存活时间的不同还分为了年轻代、老年代两个区域。而年轻代还被进一步划分成了Eden区、From Survivor0区、To Survivor1区。如图:
image.png
当有对象需要内存分配的时候,优先分配到了年轻代的Eden区。等到Eden区内存不足的时候,Java虚拟机会启动垃圾回收,此时Eden区没有被引用的对象占用的内存就会被回收。而一些存活比较久的对象则会进入到老年代。在Java虚拟机中有一个名为-XX:MaxTenuringThreshold 的参数专门用来统计一个对象从年轻代进入到老年代需要GC的次数。即年轻代对象GC指定次数后,下一次GC就会进入到老年代。
为什么Java堆要这样划分区域呢?
在虚拟中中必然有存活时间短的对象,也有存活时间较长的对象。如果我们将其混在一起,那么因为存活较短的对象有很多势必会导致频繁垃圾回收。而垃圾回收不得不对所有的内存都进行扫描,那么其实有一部分对象存活时间较长,对他们进行扫描完全是浪费时间。因此为了提高回收的效率,就进行了分区。
2、私有部分:PC寄存器、Java 虚拟机栈、本地方法栈
以上我们知道Java堆和方法区方法区是线程公有的内存部分。而Java虚拟机内存中也有一部分是线程私有的。他们包括了:PC寄存器、Java虚拟机栈、本地方法栈。
- PC寄存器
指的是保存线程当前执行的方法。任意时刻,一条Java虚拟机线程只会执行一个方法的代码。而这个被线程执行的方法称为该线程的当前方法,其地址存在PC寄存器中。 - Java虚拟机栈
这个栈与线程同时创建。用来存储栈帧,即存储局部变量、与一些过程结果的地方。 - 本地方法栈
当Java虚拟机使用其他语言(C语言)来实现指令集解释器的时候,会使用带本地方法栈(用的比较少)
小结
学到这里我们就知道一个Java文件经过JavaC编译器编译成了字节码文件,然后字节码载入Java虚拟机内存。我们的类的信息就会存在方法区中。如果创建对象,那么我们的对象就会存在Java堆中。如果调用方法,就会用到PC寄存器、Java虚拟机栈、本地方法栈。面对如此多的Java类,Jvm是如何决定这些类的加载顺序呢?又是如何控制他们的加载呢?下一节我们讲讲JVM的类加载机制。
网友评论