每日一点JVM---JVM内存结构及调优参数

JVM内存结构

首先我们来看一张图,这张图可能是很多人第一次学JVM看到的图片。
每日一点JVM---JVM内存结构及调优参数
通过这个图我们可以看到,JVM内存结构可以大概分为两块,一块是线程共享的(分别是方法区),另一部分是线程私有的(虚拟机栈本地方法栈程序计数器)。
手绘图片一 (待补充)
在类加载的时候,jvm首先会帮助我们把类中的方法存入方法区中,在接下来执行字节码指令的时候,就需要程序计数器来帮助我们记住运行到哪一行字节码,紧接着就是在对应的线程内,使用虚拟机栈本地方法栈以栈帧的方式来调用方法,当然,这期间可以会引用到Java堆上的对象。

接着是各个区域的作用:

1. 方法区(Method Area)

方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,他还有一个别名叫做非堆(Non-Heap),目的是与Java对区分开来。
在JDK 1.8之前,代表JVM中的一块区域,主要是放从class文件加载进来的类,还有一些类似于常量池的东西放在这里。在JDK 1.8之后,区域的名字改了,叫做MetaSpace(元数据空间)
根据虚拟机规范的设定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

2. 程序计数器(Program Counter Area)

编译器将.java文件编译成.class文件(字节码文件),而字节码文件中的字节码指令会一条条执行。
而程序计数器用于记录当前执行的字节码指令的位置,另外,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每个线程都要有一个独立的程序计数器,各个线程间互不影响,独立存储
如果线程正在执行的是一个Java方法,那么计数器记录的是字节码指令的地址;如果正在执行的是Native方法,这个计数器值为空(Undefined)。
此区域是唯一一个在Java虚拟机中规范中没有规定任何OutOfMemoryError情况的区域。

3. 虚拟机栈(VM Stack)

Java虚拟机栈(Java Virtual Machine Stacks)的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表存放了编译器可知的各种基本数据类型对象引用类型(referrence类型,不同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出*Error异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
手绘图片二 (待补充)

4. 本地方法栈(Native Method Stack)

本地方法栈的功能与虚拟机栈相似,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
与虚拟机栈一样,本地方法栈区域也会抛出*Error和OutOfMemoryError异常。

5. Java堆(Java Heap)

对大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代老年代;再细致一点的有Eden空间From Survivor空间To Survivor空间等。
根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和**-Xms控制)。
如果在堆中
没有内存完成实例分配,并且堆也无法再扩展**时,将会抛出OutOfMemoryError异常。
手绘图片三 (待补充)

然而今晚在查看博客的时候,看到了@博主“纯洁的微笑”的博文写得很好,故此引用部分来进行学习,共同进步。
每日一点JVM---JVM内存结构及调优参数
此图片把Java内存区域分为堆内存、方法区三大块。堆内存其实是JVM中最大的一块,由年轻代老年代组成,而年轻代又被分成Eden空间From Survivor空间和To Survivor空间,默认情况下年轻代会按照8:1:1的比例来分配内存。这里的Eden其实是圣经里伊甸园(亚当夏娃的居住地)的意思,而Survivor则是存活区的意思。而这三个小分区其实也跟垃圾回收有着莫大的关系。

JVM调优参数

JVM调优参数,顾名思义是用来对JVM进行调优的。如何调优,则关系到上面内存区域大小的分配,因此这些参数都是用来设置内存区域大小的。

-Xms 设置Java堆内存的最小大小
-Xmx 设置Java堆内存的最大大小
-XX:NewSize 设置新生代最小空间大小
-XX:MaxNewSize 设置新生代最大空间大小
-XX:PermSize 设置永久代最小空间大小
-XX:MaxPermSize 设置永久代最大空间大小
-Xss 设置每个线程的栈内存大小

这里我们可以看到,并没有可以设置老年代大小的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制:
老年代空间大小=堆空间大小-新生代空间大小