JVM调优艺术:JVM内存管理机制深度剖析

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

对于栈这个数据模型,它的特征就是先进后出,后进先出。虚拟机栈也不例外。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堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

堆的大小参数:

2.5.1 使用HSDB工具从底层深入理解运行时数据区

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

HSDB是JDK自带的Hotspot Debuger工具,透过它能够让我们更直观地查看运行中的java对象在内存中的存在形式和状态,如对象的oops、类信息、线程栈信息、堆信息、方法字节码和JIT编译后的汇编代码等。

对下面这段代码进行内存的分析

public class Test {

public final static String MAN_TYPE = “man”;

public static String WOMAN_TYPE = “woman”;

public static void main(String[] args)throws Exception{

Person p1 = new Person();

p1.name = “niuniu”;

p1.age = 18;

p1.sex = MAN_TYPE;

// 垃圾回收15次

for(int i = 0;i<15;i++){

System.gc();

}

Person p2 = new Person();

p2.name = “yangyang”;

p2.age = 19;

p2.sex = WOMAN_TYPE;

Thread.sleep(Integer.MAX_VALUE);

}

}

class Person{

String name;

String sex;

int age;

}

复制代码

2.5.1.1 打开HSDB

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

要是用HSDB,第一步,必须把sawindbg.dll复制到对应目录的jre下,否则运行HSDB时,就会报这样的错误:

复制到Java目录下的jre的bin目录下,在我的电脑中就是C:\Program Files\Java\jre1.8.0_271\bin

第二步:在cmd中,打开sa-jdi.jar所在的目录,使用命令,java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB 打开HSDB工具。

2.5.1.2 向HSDB中添加进程

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

使用jps找到所要添加的进程

可以看到,2732就是我们所要找的进程号,把2732添加到HDSB中。

第一步:选择File -> Attach to HotSpot Process

第二步:在弹出的对话框中输入进程号2732

然后就可以看到要找的线程main

2.5.1.3 分析栈内的内存分布情况

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

点击如图所示的stack memory按钮

最后

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

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
GZX-1715514441061)]

[外链图片转存中…(img-1rgCnMPR-1715514441062)]

[外链图片转存中…(img-gbwDzdLX-1715514441062)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值