JVM

2021-03-23  本文已影响0人  ZoranLee

JVM

翻译

image.png

跨平台

image.png

跨语言

JVM、JDK、JRE

JRE

JVM只是一个翻译,把 Class翻译成机器识别的代码,但是需要注意,JVM不会自己生成代码,需要大家编写代码,同时需要很多依赖类库,这个时候就需要用到JRE。

JDK

JVM整个流程:

一个 Java 程序,首先经过 javac 编译成 .class 文件,然后 JVM 将其加载到方法区,执行引擎将会执行这些字节码。执行时,会翻译成操作系统相关的函数。JVM 作为 .class 文件的翻译存在,输入字节码,调用操作系统函数。

我们所说的 JVM,狭义上指的就 HotSpot(因为JVM有很多版本,但是使用最多的是HotSpot)。如非特殊说明,我们都以 HotSpot 为准。Java 之所以成为跨平台,就是由于 JVM 的存在。Java 的字节码,是沟通 Java 语言与 JVM 的桥梁,同时也是沟通 JVM 与操作系统的桥梁。

运行时数据区域

JVM的划分

image.png

程序计数器

1、程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。

2、由于 Java是多线程语言,当执行的线程数量超过 CPU 核数时,线程之间会根据时间片轮询争夺 CPU 资源。

如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令

3、程序计数器也是JVM中唯一不会OOM(OutOfMemory)的内存区域;

虚拟机栈

虚拟机栈

哪怕你只有一个 main() 方法,也是以线程的方式运行的。
在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。

栈帧

在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。

一旦完成相应的调用,则出栈。

所有的栈帧都出栈后,线程也就结束了。

每个栈帧包含四个区域

顾名思义就是局部变量的表,用于存放我们的局部变量。

首先它是一个32位的长度,主要存放我们的Java的八大基础数据类型,一般32位就可以存放下,

如果是64位的就使用高低位占用两个也可以存放下,

如果是局部的一些对象,比如我们的Object对象,我们只需要存放它的一个引用地址即可。

存放我们方法执行的操作数的,它就是一个栈,先进后出的栈结构,

操作数栈,就是用来操作的,操作的的元素可以是任意的java数据类型,

所以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的,

操作数栈运行方法就是JVM一直运行入栈/出栈的操作

Java语言特性多态(需要类运行时才能确定具体的方法)。

正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)

栈的大小缺省为1M,可用参数 –Xss调整大小,例如-Xss256k

栈帧执行对内存区域的影响

https://cloud.tencent.com/developer/article/1333540

image.png

在JVM中,基于解释执行的这种方式是基于栈的引擎,这个说的栈,就是操作数栈。

本地方法栈

本地方法栈跟 Java 虚拟机栈的功能类似,Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。

但本地方法并不是用 Java 实现的,而是由 C 语言实现的。

本地方法栈是和虚拟机栈非常相似的一个区域,它服务的对象是 native 方法。你甚至可以认为虚拟机栈和本地方法栈是同一个区域。 虚拟机规范无强制规定,各版本虚拟机自由实现 ,HotSpot直接把本地方法栈和虚拟机栈合二为一 。

线程共享的区域

方法区/永久代 很多开发者都习惯将方法区称为“永久代”,其实这两者并不是等价的。

HotSpot 虚拟机使用永久代来实现方法区,但在其它虚拟机中,例如,Oracle 的 JRockit、IBM 的 J9 就不存在永久代一说。

因此,方法区只是 JVM 中规范的一部分,可以说,在 HotSpot 虚拟机中,设计人员使用了永久代来实现了 JVM 规范的方法区。

方法区主要是用来存放已被虚拟机加载的类相关信息,包括类信息、静态变量、常量、运行时常量池、字符串常量池。

JVM 在执行某个类的时候,必须先加载。

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

字面量包括字符串(String a=“b”)、
基本类型的常量(final 修饰的变量),
符号引用则包括类和方法的全限定名(例如 String 这个类,它的全限定名就是 Java/lang/String)、
字段的名称和描述符以及方法的名称和描述符。

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

方法区与堆空间类似,也是一个共享内存区,所以方法区是线程共享的。

假如两个线程都试图访问方法区中的同一个类信息,而这个类还没有装入 JVM,那么此时就只允许一个线程去加载它,另一个线程必须等待。

在 HotSpot 虚拟机、Java7 版本中已经将永久代的静态变量和运行时常量池转移到了堆中,其余部分则存储在 JVM 的非堆内存中,而 Java8 版本已经将方法区中实现的永久代去掉了,并用元空间(class metadata)代替了之前的永久代,并且元空间的存储位置是本地

jdk1.8以后大小就只受本机总内存的限制(如果不设置参数的话)

Java8 为什么使用元空间替代永久代,这样做有什么好处呢?

官方给出的解释是: 移除永久代是为了融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为 JRockit 没有永久代,所以不需要配置永久代。 永久代内存经常不够用或发生内存溢出,抛出异常 java.lang.OutOfMemoryError: PermGen。

这是因为在 JDK1.7 版本中,指定的 PermGen 区大小为 8M,由于 PermGen 中类的元数据信息在每次 FullGC 的时候都可能被收集,回收率都偏低,成绩很难令人满意;还有,为 PermGen 分配多大的空间很难确定,PermSize 的大小依赖于很多因素,比如,JVM 加载的 class 总数、常量池的大小和方法的大小等。

堆是 JVM 上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。
我们常说的垃圾回收,操作的对象就是堆。

堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。

随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java 中,就叫作 GC(Garbage Collection)。

那一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?这和两个方面有关:对象的类型和在 Java 类中存在的位置。 Java 的对象可以分为基本数据类型和普通对象。 对于普通对象来说,JVM 会首先在堆上创建对象,然后在其他地方使用的其实是它的引用。比如,把这个引用保存在虚拟机栈的局部变量表中。

对于基本数据类型来说(byte、short、int、long、float、double、char),有两种情况。

当你在方法体内声明了基本数据类型的对象,它就会在栈上直接分配。其他情况,都是在堆上分配。

堆大小参数: -Xms:
堆的最小值; -Xmx:
堆的最大值; -Xmn:
新生代的大小; -XX:NewSize;
新生代最小值; -XX:MaxNewSize:
新生代最大值; 例如- Xmx256m

直接内存

不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;如果使用了NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;

这块内存不受java堆大小限制,但受本机总内存的限制,可以通过-XX:MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常。

上一篇 下一篇

猜你喜欢

热点阅读