乐趣区

关于JVM:JVM内存模型你看这一篇就够了

摘要:JVM 是一种用于计算设施的标准,是一个虚构进去的计算机,通过在理论的计算机上仿真模仿各种计算机性能来实现的。

本文分享自华为云社区《[[云驻共创]JVM 内存模型的探知之旅 ](https://bbs.huaweicloud.com/b…)》,作者:多米诺的古牌。

1. JVM 介绍

1.1 什么是 JVM?

JVM 是 Java Virtual Machine(Java 虚拟机)的简称,是一种用于计算设施的标准,是一个虚构进去的计算机,通过在理论的计算机上仿真模仿各种计算机性能来实现的。

1.2 JVM 的长处

1.2.1 一次编写,到处运行。

JVM 能够让 java 程序,一次编写,导出运行。让底层代码和运行环境分来到,编写好一份代码后,不必再次批改内容,只用通过装置不同的 JVM 环境主动进行转换即可运行,在各种零碎中无缝连贯。

1.2.2 主动内存治理,垃圾回收机制。

在 Java 诞生之时,C 和 C ++ 称霸天下,然而这两种语言中没有内存管理机制,是通过手动操作来进行的治理,十分麻烦和繁琐。

此时 Java 应运而生, 为了解决内存治理这个方面,专门设计了垃圾回收机制,来主动进行内存的治理。极大的优化了操作,让程序员们不必正在噼里啪啦在码代的陆地中漫游时,还要操心内存会不会溢出这些“影响我方输入”的问题,登时取得了成吨的好评。

1.2.3 数组下标越界查看

在 Java 诞生之时,还有个让过后 C 和 C ++ 大佬头疼的问题是,数组下标越界是没有查看机制的,这还了得,又是一个影响“我方暴力输入”的罪魁祸首,因而 JVM 持续抱着暖男的思维,又来了个爱的抱抱。

JVM 又一次看见了大佬们的懊恼,果决提供了数组下标越界的主动查看机制,在检测到数组下标呈现越界后,会在运行时主动抛出“java.lang.ArrayIndexOutOfBoundsException”这个异样,在过后可是打动了很多业界大佬(我猜的)。

1.2.4 多态

JVM 还有一个多态性能,是通过雷同接口,不同的实例进行实现,实现不同的业务操作,比方:定义了一个动物接口(外面有一个吃的办法),咱们就能够通过这个动物发明小猫(吃鱼),再发明一个狗狗(吃肉),再发明一个小助手(吃零食,O(∩_∩)O 哈哈~)。

认真想想,对咱们有啥影响呢,那益处老多了,比方:

(1)打消类型之间的耦合关系;

(2)可替换性;

(3)可扩充性;

(4)接口性;

(5)灵活性;

(6)简化性;

1.3 JVM、JRE、JDK 之间的关系

1.3.1 JVM 的简介

JVM 是 Java Virtual Machine 的简称,是 Java 虚拟机,是一种模仿进去的虚构计算机,它通过在不同的计算机环境当中模仿实现计算性能来实现的。

引入 Java 虚拟机后,Java 语言在不同平台上运行时就不须要从新编译。在其中,Java 虚拟机屏蔽了与具体平台的相干信息,使得 Java 源程序在编译实现之后即可在不同的平台运行,达到“一次编译,到处运行”的目标,Java 语言重要的特点之一跨平台,也即与平台的无关性,其关键点就是 JVM。

1.3.2 JRE 的简介

JRE 是 Java Runtime Environment 的简称,是 Java 运行环境,是让操作系统运行 Java 应用程序的环境,其外部蕴含 JVM,也就是说 JRE 只负责对曾经存在的 Java 源程序进行运行的操作,它不蕴含开发工具 JDK,对 JDK 外部的编译器、调试器和其它工具均不蕴含。

1.3.3 JDK 的简介

JDK 是 Java Development Kit 的简称,是 Java 开发工具包,是整个 Java 程序开发的外围。其次要蕴含了 JRE、Java 的零碎类库以及对 Java 程序进行编译以及运行的工具,例如:javac.exe 和 java.exe 命令工具等。

1.4 JVM 的常见实现

Oracle(Hotspot、Jrockit)、BEA(LiquidVM)、IBM(J9)、taobaoVM(淘宝专用,对 Hotspot 进行了深度定制)、zing(垃圾回收机制十分快,达到 1 毫秒左右)。

1.5 JVM 的内存结构图

当 Java 程序编译实现为.class 文件 ==》类加载器(ClassLoader)==》将字节码文件加载进 JVM 中;

1.5.1 办法区、堆

办法区中保留的次要是类的信息(类的属性、成员变量、构造函数等)、堆(创立的对象)。

1.5.2 虚拟机栈、程序计数器、本地办法栈

堆中的对象调用办法时,办法会运行在虚拟机栈、程序计数器、本地办法栈中。

1.5.3 执行引擎

执行办法中代码时,代码通过执行引擎执行中的“解释器”执行;办法中常常调用的代码,即热点代码,通过“即时编译器”执行,其执行速度十分快。

1.5.4 GC(垃圾回收机制)

GC 是针对堆内存中没有援用的对象进行回收,能够手动也能够主动。

1.5.5 本地办法接口

因为 JVM 不能间接调用操作系统的性能,只能通过本地办法接口来调用操作系统的性能。

2. JVM 内存构造 - 程序计数器

2.1 程序计数器的定义

Program Counter Register 即程序计数器(寄存器),用于记录下一条 Jvm 指令的执行地址。

2.2 操作步骤

javap 次要用于操作 JVM,javap -c 是对 java 代码进行反汇编操作。下图为通过先编译 demo.java 后,再执行 javap -c demo 的输入后果:

其中第一列为二进制字节码,即 JVM 指令,第二列为 java 源代码。第一列中的序号为 JVM 指令的执行地址。

JVM 会通过程序计数器记录下一条须要执行的 JVM 指令的地址(比方第一行的 0),而后交给解释器解析为机器码,最初交给 cpu(只能辨认机器码),实现一行的执行。

想要执行下一行,持续让 JVM 的程序计数器记录下一条地址(比方第二行的 3),再交给解释器解析后给 cpu,以此类推执行完结。

2.3 特点

2.3.1 线程公有的

2.3.2 不会存在内存溢出

3. JVM 内存构造 - 虚拟机栈

3.1 定义

虚拟机栈是每个线程运行所须要的内存空间,每个栈中由多个栈帧组成,每个线程中只能有一个流动栈帧(对应以后正在执行的办法),所有栈帧都遵循后进先出,先进后出的准则。

栈帧是每次调用办法时所占用的内存,在栈帧中保留的内容参数、局部变量、返回地址。

注 1:垃圾回收不波及栈内存,因为栈内存是由办法调用产生的,当办法调用完结后会弹出栈。

注 2:栈内存不是调配的越大越好,因为物理内存是肯定的,栈内存越大,能够反对更多的递归调用,然而可执行的线程数会越来越少。

注 3:办法的局部变量,当其没有逃离办法的作用范畴时,是线程平安的;如果其援用了对象(比方动态变量,即共享变量,用对象作为参数的办法,返回值为对象的办法),并且逃离出了办法的作用范畴,就须要思考线程平安的问题了。

3.2 栈内存溢出

3.2.1 产生起因

(1)虚拟机栈中,栈帧过多(有限递归),如图 1 栈帧过多;

(2)每个栈帧所占用过大,如图 2 栈帧过大。

3.2.2 栈内存溢出小试验

3.2.2.1 栈帧过多的小试验

有限递归调用(栈帧过多)的小试验,method1() 办法在主办法中有限调用本人,那么会产生什么状况呢?

答案很显著,程序解体了,产生了栈内存溢出谬误,如下图所示:

-Xss: 该参数规定了每个线程虚拟机栈的大小;

接着咱们通过设置一个虚拟机栈的大小是 256k 试试会产生什么?


咱们发现当咱们调整了虚拟机栈的大小后执行了 4315 次办法后内存就溢出了,而调整虚拟机栈之前,咱们是 23268 次,很显著咱们能够通过 -Xss 参数调整虚拟机栈的大小来管制内存的溢出状况。

3.2.2.2 线程运行诊断小试验

设想中的场景,大佬在疯狂输入,忽然 CPU 爆表了,显示 CPU 占用过多,如何去定位哪行代码的问题,是的是哪行(大佬都很忙的好吗,当然要准确了,一分钟几千万高低的,O(∩_∩)O 哈哈~)?

Linux 环境下:

在后盾运行 Stack_6 这个 java 字节码(.class)文件:

** 注:无论是否将 nohup 命令的输入重定向到终端, 输入都将附加到当前目录的 nohup.out 文件中。
**

(1)通过 top 命令,查看过程(相当于工作管理器),发现了一个占用 CPU 达到 100% 的可疑家伙,这还了得,连忙瞅瞅具体产生了什么,还有没有王法,这让其余小伙伴还怎么欢快的游玩,秒速纠错 ING。。。

注:top 命令,查看哪个过程占用 CPU 过高,返回过程号。

(2)通过 ps H -eo pid,tid ,%cpu | grep 命令过滤工作管理器中的内容。

注:ps H -eo pid,tid,%cpu |grep,是通过 ps 命令查看哪个线程占用 CPU 过高,返回过程 id,其中 pid 为过程 id,tid 为线程 id,%cpu 为 CPU 占用状况;

发现了罪魁祸首,这一串串六神无主的红色。。。

(3)通过 jstack 过程 id 查看,20389 这个有问题的过程中具体的状况。

注:jstack 过程 id,是通过 jstack 命令定位具体哪段代码呈现占用 CPU 过高,留神 jstack 命令查找的线程 id 是 16 进制的,须要转换;

发现外面有一堆执行的代码,那么咱们怎么找到具体是哪个家伙搞事件的呢?上图咱们能够发现搞事件的线程是 20441,那么咱们通过计算器将 20441 转换为 16 进制的 4FD9 再去试试,假相只有一个。

通过比照 nid(线程 id)4fd9,咱们发现这个叫 thread1 的线程始终在运行(RUNNABLE 状态),并且查看到地位是位于 Stack_6.java 文件的第 11 行呈现的问题。。。

当初咱们回到源码中,在 Stack_6 文件的第 11 行,咱们发现原来这里始终在执行死循环,终于找到你,还好我没放弃,奈斯~

4. JVM 内存构造 - 本地办法栈

4.1 定义

因为 Java 自身有时候是无奈间接和操作系统底层交互的,但有时候须要 Java 调用本地的 C 或 C ++ 办法,所以此时本地办法栈应运而生,它们是一类带有 native 关键字的办法。

5. JVM 内存构造 - 堆

5.1 定义

Heap 堆:是通过 new 关键字创立的对象寄存在堆中

5.2 特点

5.2.1 线程共享

堆中寄存的对象都是线程共享的,因而都是须要思考线程平安问题的。

5.2.2 有垃圾回收机制

因为堆中寄存的对象寄存了大量的对象,因而给他配了个小助手——垃圾回收机制(能够调自动挡和手动挡哦~)。

5.3 堆内存溢出小试验

5.3.1 批改堆内存大小参数的小试验

持续空想一个场景,当一个大佬开发完一个段代码的时候(当然个别大佬都是很自信的,我写的代码怎么可能有问题,不存在的。。。),然而测试可跑不了,稳当起见咱们还是默默得搞测试试试嘛,平安第一。然而机器的内存就这么大,大佬必定跑了很屡次了,都没呈现问题的,这不是找茬嘛。。。还是默默改下机子参数再试试吧(想去怼大佬,肯定要拿出证据嘛~)。

-Xmx:JVM 调优参数之一,该参数示意 java 堆能够扩大到的最大值。上面上案例:

在执行了 26 次之后,果决的后盾报了堆内存溢出谬误。

上面通过 -Xmx JVM 调优参数将堆内存调小至 8m,再试试会产生什么呢?


操作根本和栈内存溢出的时候的案例一样,次数显著变小了,只调用了 17 次就呈现了堆内存溢出谬误了。

5.3.2 堆内存诊断的小试验

jps 工具: 查看以后零碎中有哪些 java 过程

jmap 工具:查看堆内存的占用状况 jmap -heap 过程 id

jconsole 工具:图形化的工具,领有多功能的监测性能,能够间断监测。

上面咱们通过运行代码后通过 jconsole 可视化图形工具,来查看堆内存的应用状况。


上图咱们能够看到,在咱们创立 10mb 的数组对象时,内存应用有肯定回升;而后在咱们手动调用垃圾回收机制后,内存又失去了很大的开释。

6. JVM 内存构造 - 办法区

6.1 定义

Java 虚拟机中有一个被所有 jvm 线程共享的办法区。办法区有点相似于传统编程语言中的编译代码块或者操作系统层面的代码段。它存储着每个类的结构信息,譬如运行时的常量池,字段,办法数据,以及办法和构造方法的代码,包含一些在类和实例初始化和接口初始化时候应用的非凡办法。

办法区有个别称 non-heap(非堆),能够看作是一块独立于堆的内存空间,是 JVM 标准中定义的一个概念,用于存储类信息、常量池、动态变量,JIT 编译后的代码等数据,具体放在哪里,不同的实现能够放在不同的中央。

6.2 特点

(1)办法区与 java 堆一样,是各个线程共享的内存区域;

(2)办法区在 JVM 启动的时候被创立;

(3)办法区的大小,跟堆空间一样,能够抉择固定大小或扩大;

(4)办法区的大小决定了零碎能够保留多少个类,如果零碎定义了太多的类,导致办法区溢出,虚拟机同样会抛出溢出谬误 OutOfMemoryError;

(5)敞开 JVM 就会开释这个区域的内存。

6.3 JVM 内存构造示意图

在 JVM 内存构造 1.6 的时候,办法区保留在内存构造中,叫做永恒代,外面存储了运行时的常量池(蕴含串池 StringTable)、类的信息、类加载器;

在 JVM 内存构造 1.8 的时候,办法区做为一个概念,保留在本地内存中,叫做元空间,外面存储了运行时的常量池、类的信息、类加载器,此时串池(StringTable)贮存在堆之中。

点击关注,第一工夫理解华为云陈腐技术~

退出移动版