JVM内存管理

目标:

1.JVM是什么?

2.JVM内存怎么分区?按照什么方式分区?为什么要进行分区管理?

3.分区数据怎么管理?

一、JVM是什么

1.1 JVM规范

JVM是一种规范,是JVM是虚拟机内存管理的规范和标准。Oracle公司提供了Java虚拟机规范文档,符合规范的语言都可以运行的JVM上。当然,用户也可以自己按照JVM的规范,设计自己的虚拟机,然后通过Oracle官方认证,就可以成为认可的JVM。

因此,JVM具有以下特性:

JVM跨平台型:可以运行的在不同的设备终端,包括Windows\Linux\Unix\Android\Mac等;

JVM语言无关性:遵循JVM规范的语言都可以运行在JVM上。例如Java\Scala\Kotlin\groovy等。

常见的JVM实现:

1.2 Java程序运行过程

Java程序从编译到执行的过程

编译:使用javac 将Java程序编译为字节码文件,helloWorld.java --》helloWorld.class文件

加载:通过类加载器(ClassLoader)将字节码加载到方法区,

执行:然后交给JVM执行引擎进行进行。JVM执行引擎会把字节码文件翻译为 操作系统OS识别的机器码进行执行。

1.3 JDK、JRE与JVM关系

JDK是一个大的合集,包括JRE和JVM。包括编译工具javac、Java运行时环境JRE.

JRE:Java运行时环境。它包含了Java虚拟机(JVM)和Java核心类库,但不包括Java开发工具(如编译器和调试器)。这意味着JRE是Java程序运行时的最小环境,而JDK(Java开发工具包)则提供了开发、编译和调试Java应用程序所需的完整工具集。

JVM:Java虚拟机,主要负责类加载和执行,垃圾回收。

1.4 解释执行和JIT

解释执行:字节码解释器,将class文件翻译为操作系统认识的机器码(一般是汇编程序或者二进制执行程序),交由操作系统执行。因为需要经过一次翻译,效率相对于直接编译为机器码的语言要低。

JIT执行:热点代码是指程序中的一些代码、方法,执行次数达到一定后(后端一般是1万+),作为热点代码。JIT编译器会将Java代码(class代码)直接翻译为机器码执行,提高效率。翻译面板存在于JVM的Codecache。Oracle HotSpot虚拟机支持JIT。

二、JVM分区管理

2.1 运行时数据区

什么是运行是数据区?Java虚拟机在执行Java程序的过程中会把它所管理的内存(JVM)划分不同的数据区域。

运行时数据库采用的是JVM内存分区管理的机制。分区管理使内存管理更加规范化。

2.2 JVM内存分区

运行时数据区按照功能划分为:线程共享(方法区和堆)和线程私有(虚拟机栈、本地方法栈、程序计数器)

方法区:方法区主要存储了类的结构信息(Clazz)、运行时常量、静态变量, HotSpot中称为永久代、元空间(MataSpace)。

:存储对象实体,JVM中空间最大的一部分。堆是容易发生内存溢出的主要分区。

虚拟机栈:Java 虚拟机栈的生命周期和线程相同,Java 虚拟机栈描述的是 Java 方法执行的。Java程序的执行对应的一系列方法的调用,一个方法对应栈中一个栈帧,因此虚拟机栈也称为方法栈。

本地方法栈:Java本地方法(Native调用的方法,如C++/C程序)执行的栈。Native方法的执行在本地方法栈中执行(也是方法栈帧的方式)。

程序计数器:是一个较小的内存空间,用于存储指向下一条指令的地址。程序计数器分区永远不会发生内存溢出。

2.3 直接内存

进程中还有一部分,没有经过JVM进行虚拟化的部分--直接内存,不属于 JVM统一管理。

三、Java方法运行与虚拟机栈

3.1 虚拟机栈

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

3.1.1 修改栈大小限制-Xss

默认1M大小

3.1.2 栈溢出

栈内存不足时会发生栈溢出。栈溢出常见的两种情况:

方法调用死循环:方法调用出现死循环,一直不停的创建栈帧,直到栈溢出

创建大对象:创建的栈对象超出了最大栈空间大小。例如栈限制大小2M,创建一个3M的栈数组。

3.2 栈帧

Java执行方法时,会生成一个栈帧,压入虚拟机栈。

线程执行方法 main-->A()-->B(),执行时栈内存中情况

栈帧的管理先进后出的规则(栈存储的特点) 

3.2.1 栈帧结构

3.3 Java方法运行内存

简单的程序演示java程序运行流程

我们看work方法的执行过程

3.3.1 方法执行流程

1) woke方法栈帧入栈

方法区work方法的代码,创建一个栈帧,用于work方法的执行,并且将栈帧压入虚拟机栈。

字节码左侧数字: 字节码地址,字节码指令在work方法中的偏移量,

字节码指令:数字后面是字节码指令

2) 执行第一条指令 iconst_1: 程序计数器修改指令地址为0,执行0号指令。

创建一个int类型的常量,值为1,

3)执行第1号指令 istore_1: 将操作数栈中的数据弹出来(POP出栈),放入局部变量表 1 号位置。程序计数器指向方法 1 位置。

istore是写入一个整数值。

4)同样的方法执行指令2号指令,程序计数器变为2,创建一个整型常量,值为2,压入操作数栈;

执行指令3号指令,程序计数器变为3,操作数栈数据出栈,存入局部变量表 2 号位置。

5)执行第4号指令,程序计数器变为4,iload_1指令,把局部变量表下标为1的整数,压入操作数栈;

执行5号指令,将局部变量表2号位置的整数压入操作数栈,程序计数器变为5,操作数栈压入局部表量表2号位置的整数,值为2,位于操作数栈栈顶。

6)指令6号指令,程序计数器变为6,执行指令iadd, 先把两个操作数分别出栈;

然后计算,2+1=3,

将计算结果重新压入操作数栈

7) 执行7号指令,bipush 10,将整数10 压入操作数栈,这个是双字节指令,程序计数器变为7

 

8)执行9号指令,程序计数器变为9,imul两个整数相乘,操作数栈中的两个整数分别出栈,计算得到结果:10*3=30;将计算结果重新压入操作数栈。

 9)执行第10号指令,程序计数器变为10,istore_3, 将操作数栈中的整数出栈,存入局部变量表3号位置。

10)执行11号指令,程序计数器变为10,iload_3,将局部变量表3号位置的整型数(30),加载到操作数栈中。

11)执行指令12号,程序计数器变为12,ireturn, 操作数栈中的整型数(30)出栈,work栈帧出栈,返回main-栈帧,返回值30,返回位置在完成出口记录(main方法的3号位置),在main方法

好位置继续接着执行。work方法栈帧结束生命周期,释放work栈帧内存。

3.2.2 本地方法栈

Java直接操作不了线程,需要通过本地方法,调用线程操作。运行到本地方法时,因为不是字节码程序,不能得到对应的指令地址,程序计数器置位null

3.4 运行时数据区其他区域

3.5 对象在JVM中的分配

四、从底层深入理解运行时数据区

JHSDB工具,位于SDK/lib/sa-jdi.jar

启动方法:

java -cp [sa-jdi.jar路径] sun.jvm.hotspot.HSDB

1) attach需要查看的进程

2)查看堆的话,能够看到对内存分区的物理地址范围

Java新生代进阶老年代,15岁进阶老年代,因为表示年龄的4个bit位,1111,最大为15.

面试题

1.JVM为什么需要采用分区管理模式?

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值