JVM调优艺术:JVM内存管理机制深度剖析,mybatis面试题常问

Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分成若干个不同的数据区域。

2.分类

====

这些不同的数据区域分为:虚拟机栈、本地方法栈、程序计数器、方法区、堆。其中前三个区域是线程私有的区域,后两个区域是由所有线程共享的数据区。

2.1 程序计数器:

==========

2.1.1 定义

========

指向当前线程正在执行的字节码的指令地址。

2.1.2 为什么需要程序计数器?(时间的轮转机制)

==========================

2.1.3 为什么程序计数器是JVM中唯一不会OOM的区域?

==============================

程序计数器是一块很小的内存区域,它只需要记录程序运行的地址,一个int类型的长度就足够了。

2.2 虚拟机栈:

=========

2.2.1 定义

========

存储当前线程运行方法所需的数据、指令、返回地址。

对于栈这个数据模型,它的特征就是先进后出,后进先出。虚拟机栈也不例外。Java中每一个方法在执行的同时,都会创建一个栈帧,栈帧中存储了局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法调用到结束的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。 如果把虚拟机栈比喻成子弹夹,那么栈帧就是子弹。

2.2.2 局部变量表

===========

2.2.2.1 定义

==========

所谓局部变量表,就是在方法执行时,各种局部变量存放的地方。

局部变量表可以存放的数据类型只有8种基本类型、引用类型和returnAddress类型。引用类型它是指向对象起始地址的引用指针或者指向一个代表对象的句柄。returnAddress类型,是指向一条字节码指令的地址,当带有返回值的方法完成时,方法完成就要出栈,出栈的地址在哪,就是使用这个值记着的。一般来说,是在该方法的下一行。 局部变量表所需的空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,方法运行期间不会改变局部变量表的大小。

2.2.2.2 异常处理

============

Java虚拟机规范中,对这个区域规定了两种的异常状况:StackOverflowError和OutOfMemoryError。 如果线程请求的栈深度>虚拟机栈的深度,将抛出StackOverflowError异常。如果虚拟机栈可以动态扩展,如果在扩展时无法申请到足够的内存,将抛出OutOfMemoryError。

2.2.2.3 方法执行时,栈帧是如何工作的?

=======================

public class Person{

public int work(){

int x = 1;

int y = 2;

int z = (x+y)*10;

return z;

}

public static void main(String[] args){

Person person = new Person();

person.work();

}

}

复制代码

这样的一段代码,使用javac编译之后,得到Person.class文件,使用Javap -c进行反汇编,得到下面的代码:

分析一下这段代码执行时,虚拟机栈中的内存变化是怎样的。 执行过程:

  1. 执行main(),main()的栈帧入栈

  2. 执行work(),work()栈帧将main()栈帧压入栈底

  1. 执行work()中的int x = 1;

  1. 执行work()中的int y = 2;

5.执行代码 int z = (x+y)*10; 执行完成之后,操作数栈清空

6 最终,11: iload_3 12: ireturn 将局部变量表中下标为3的值压入操作数栈中,作为返回

以上就是work()执行时,在虚拟机栈中内存的变化过程

JVM指令集可以参照腾讯云社区的这篇 java虚拟机 JVM字节码 指令集 bytecode 操作码 指令分类用法 助记符

2.3 本地方法栈

=========

本地方法栈和虚拟机栈发挥的作用是很类似的,只不过虚拟机栈用于管理Java方法的调用,而本地方法栈则用于管理Native方法的调用。本地方法栈和虚拟机栈十分类似,虚拟机规范对其中方法使用的语言、使用方式和数据结构并没有强制规定,因此各虚拟机可以自由的实现它。HotSpot虚拟机,直接把本地方法栈和虚拟机栈合二为一。

2.4 方法区

=======

方法区用于存储已被虚拟机加载的类信息(ClassLoader加载类信息就加载在这里)、常量、静态变量、即时编译器编译后的代码等数据。是所有线程共享的区域。

JVM在执行某个类的时候,必须先加载,在加载类(加载、验证、准备、解析、初始化)的时候,JVM会先加载class文件,而在class文件中除了有类的版本、字段、方法和接口等描述信息外,还有一项信息是常量池,用于存放编译期间,生成的各种字面量和符号引用。

字面量

===

字符串(String a = “b”)、基本类型常量(final修饰的变量)

符号引用

====

类和方法的全限定名(“Java/lang/String”)、字段的名称和描述符、方法的名称和描述符

当类加载到内存中后,JVM就会将class文件常量池中的内容存放到运行时常量池中;在解析阶段,JVM会把符号引用替换成直接引用(对象的索引值)。运行时常量池是全局共享的,多个类共用一个运行时常量池。class 文件中常量池多个相同的字符串在运行时常量池只会存在一份。

在JDK1.7之前,在HotSpot虚拟机中,使用永久代来实现方法区。这样做的好处是,HotSpot的垃圾回收器可以像管理Java堆一样来管理这部分的内存,省去了专门为方法区编写内存管理代码的工作,但是这样做会导致别的问题发生:1. 永久代里面的数据,回收的效率很低,但堆中放的是对象和数组,是需要频繁回收的数据。如果跟堆中一样进行垃圾回收,无疑是一种资源的浪费;2. 永久代里的内存经常不使用容易发生内存溢出,永久代从Java堆中划分,它的大小仍然是受制于堆的大小,它长时间无法回收,这块区域就很容易发生内存溢出,因此,在HotSpot虚拟机,JDK1.7版本中,已经将永久代的静态变量和运行时常量池转移到了堆中。在JDK1.8之后,更是去掉了方法区中的永久代,改为元空间,元空间所的存储位置是在机器内存中,它的大小不再受制于Java堆。也就能解决永久代内存溢出的问题。

2.5 堆

=====

Java堆,是JVM所管理的内存中最大的一块。是所有线程共享的一块区域,在虚拟机启动时创建,此内存区域唯一的目的就是存放对象实例和数组,几乎所有的对象实例都在这里分配内存。

另外Java堆也是垃圾回收器管理的主要区域,随着对象的不断创建,堆空间占用越来越多,就需要不定期的对不再使用的对象进行回收。从内存回收的角度看,由现在收集器基本都采用分代算法,所以Java堆中还可以细分为新生代和老年代,新生代中又分为Eden区、From Survivor区、To Survivor区。

根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

同时减轻大家的负担。**
[外链图片转存中…(img-XvJkudjf-1710884065896)]
[外链图片转存中…(img-hJUTfteQ-1710884065896)]
[外链图片转存中…(img-PVmixG9i-1710884065897)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-XvsbF7Zd-1710884065897)]

  • 20
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值