0、需要了解得知识点
- Jvm的基本构成
- 认识Java堆
- 认识Java栈
- 认识方法区
1、Jvm的基本构成
Java的基本结构如下图所示:
- 类加载子系统:从本地或者从网络加载class文件
- 方法区:存放被加载类的信息,常量池信息。
- Java堆: 在虚拟机启动时建立,堆内存线程共享。
- 直接内存: Java的NIO库允许Java程序直接使用直接内存。直接内存是不包含在Java堆内直接向系统申请的内存。(出于性能考虑,读写频繁的场合考虑使用直接内存)
- Java栈: 线程独有,线程创建时创建。
- 本地方法栈:本地方法栈和Java栈类似,不过区别在于Java方法由Java程序调用,本地方法栈由本地方法调用(通常是C语言编写)
- 垃圾回收器是jvm的重要组成部分,垃圾回收器可以对方法区,Java堆,和直接内存进行回收。
- PC寄存器:线程私有,Java虚拟机会对每个线程创建pc寄存器。
- 执行引擎:执行引擎是Java虚拟机最核心组件之一,它负责执行虚拟机的字节码。
1.1 Java堆
常见Java堆结构将整个Java堆分为新生代和老年代。其中,新生代存放新生对象或者年龄不大的对象,老年代则存放老年对象。
新生代可能分成eden,s0,s1(s1和s0也被称为from和头区)。每经过一次新生代回收如果对象存活,那么就会进入s1或者s0,之后每一次存活年龄加1,直到年龄达到一定数值后就会进入老年区。
Java堆中存放的是类的实例对象。
堆的内存指定参数-xmx,-xms。
1.2 Java栈
-
Java栈是每个线程所独有的,在每个线程建立的时候建立,每个线程消亡的时候消亡。线程执行的基本行为就是调用函数,每次函数调用数据都是通过Java栈传递。
- Java栈的结构包含以下几个部分:
- 局部变量表
- 操作数栈
- 帧数据区
-
每一次函数调用都有一个相应的栈帧被压入Java栈中,如果在函数中调用其他函数,则会形成嵌套。栈内存有限。多层函数嵌套将可能导致内存溢出。
- 指定栈内存参数 -Xss
1.2.1 局部变量表
- 局部变量表用于保存函数的参数及局部变量,局部变量表中的变量只在当前函数调用中有效果。一旦函数结束调用,那么随着函数栈帧的销毁,局部变量表也会销毁。
- 在相同栈容量下,局部变量少的函数可以支持更深的函数调用。 long,double型局部变量占用两个字节。 int,short,byte,对象引用占用一个字节。
- 栈帧中局部变量表的槽位可以复用,如果一个局部变量过了它的作用域。那么在他作用域之后声明的局部变量就很有可能会复用过期的局部变量的槽位。从而节省资源。
- 如下代码可以看到局部变量槽位的复用:
//未发生复用
public void test1(){
int a = 0;
int b = 0;
}
//发生复用
public void test2(){
{
int a = 0;
}
int b = 0;
}
-
局部变量表的垃圾回收:
//被a引用不能被回收 public void test1(){ byte[] a = new byte[6*1024*1024]; System.gc(); } //可以回收 public void test2(){ byte[] a = new byte[6*1024*1024]; a = null; System.gc(); } //局部变量a 失效,但a还是存在局部变量表中,且数组仍然被a引用,所以不能被回收。 public void test3(){ { byte[] a = new byte[6*1024*1024]; } System.gc(); } //局部变量a失效且局部变量b复用了槽位。a被销毁,则数组可以被回收。 public void test4(){ { byte[] a = new byte[6*1024*1024]; } int b = 0; System.gc(); } //函数test1被调用返回后,栈帧被销毁。数组可以被回收。 public void test5(){ test1(); System.gc() }
1.2.2 操作数栈
操作数帧用于保存计算的中间结果,同时作为计算过程中变量的临时存储空间。
1.2.3 帧数据区
帧数据区中保存着访问常量池的指针,方便程序访问常量池
1.3 方法区
线程共享用于存储系统的类信息,如字段,方法,常量池等。
Jdk1.8之前方法区可以理解为永久区,内存大小指定参数为-XX:Permisize,-XX:Maxpermisize.
Jdk1.8开始永久区被彻底移除换成了元数据区,使用的是直接内存,内存大小指定参数为-XX:MaxMetaspaceSize.