青冥 青冥
首页
  • Java 基础
  • Java 进阶
  • Java Java 版本新特性
  • JVM
  • MySQL
  • Tomcat
  • Nginx
  • Spring 系列

    • Spring
    • Spring IOC
    • Spring AOP
    • Spring MVC
  • ORM

    • Mybatis
  • 设计模式

    • 23 种设计模式
  • 操作系统内核
  • JVM 内存模型
  • 并发同步处理
  • Atomic 原子操作
  • 队列(Queue)
  • 线程池(Thread Pool)
  • 分布式 - 消息中间件

    • 消息中间件
  • 分布式 - 存储中间件

    • 存储中间件
  • 分布式 - RPC 框架

    • RPC 框架
  • Spring Boot
  • Spring Cloud Alibaba
  • Spring Cloud Netflix
  • Git
  • Maven
  • Jenkins
  • Linux
  • 容器化

    • Docker
    • Kubernetes
  • 面试合集

    • 缓存
  • 实战项目
  • 数据结构与算法
  • 计算机原理
  • 大数据
  • 人工智能(AI)
  • 前端
  • 留言区
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 关于
  • 友情链接
🍊Gitlab (opens new window)

iByte Horizon

好记性不如烂笔头
首页
  • Java 基础
  • Java 进阶
  • Java Java 版本新特性
  • JVM
  • MySQL
  • Tomcat
  • Nginx
  • Spring 系列

    • Spring
    • Spring IOC
    • Spring AOP
    • Spring MVC
  • ORM

    • Mybatis
  • 设计模式

    • 23 种设计模式
  • 操作系统内核
  • JVM 内存模型
  • 并发同步处理
  • Atomic 原子操作
  • 队列(Queue)
  • 线程池(Thread Pool)
  • 分布式 - 消息中间件

    • 消息中间件
  • 分布式 - 存储中间件

    • 存储中间件
  • 分布式 - RPC 框架

    • RPC 框架
  • Spring Boot
  • Spring Cloud Alibaba
  • Spring Cloud Netflix
  • Git
  • Maven
  • Jenkins
  • Linux
  • 容器化

    • Docker
    • Kubernetes
  • 面试合集

    • 缓存
  • 实战项目
  • 数据结构与算法
  • 计算机原理
  • 大数据
  • 人工智能(AI)
  • 前端
  • 留言区
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 关于
  • 友情链接
🍊Gitlab (opens new window)
  • JVM

    • 虚拟机的前世今生与JVM 内存区域
      • JVM基础知识💯
        • JVM从编译到执行👍
        • 从跨平台到跨语言
        • JVM的发展
      • JVM内存区域
        • 运行时数据区域的定义
        • JAVA 方法的运行与虚拟机栈
        • 程序计数器
        • 栈帧执行对内存区域的影响
        • 本地方法栈
        • 方法区(Method Area)
        • 堆
        • 直接内存(堆外内存)
    • 深入理解JVM 内存区域
  • MySQL

  • Tomcat

  • Nginx

  • 性能调优 - 专题
  • JVM
沉梦昂志
2021-02-01
目录

虚拟机的前世今生与JVM 内存区域JVM

Java官网参考文档

https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html (opens new window)

  • JVM基础知识💯 JVM
    • JVM从编译到执行👍
    • 从跨平台到跨语言
    • JVM的发展
  • JVM内存区域
    • 运行时数据区域的定义
    • JAVA 方法的运行与虚拟机栈
    • 程序计数器
    • 栈帧执行对内存区域的影响
    • 本地方法栈
    • 方法区(Method Area)
    • 堆
    • 直接内存(堆外内存)

# JVM基础知识💯 JVM

# JVM从编译到执行👍

# Java程序的执行过程

  • 过程: Java源文件(.java)——>编译器——>字节码——>JVM——>机器码
  • 一个 Java 程序,首先Java源码经过 javac 编译成 .class 文件,然后JVM将其加载到方法区,执行引擎会执行这些字节码文件。执行时,会翻译成响应操作系统相关的函数。JVM作为 .class 文件翻译的存在,输入字节码,调用操作系统的函数。
  • JVM 全称是 Java Virtual Machine ,我们俗称 Java 虚拟机。

# 1JDK、JRE、JVM的关系

JDK、JRE、JVM关系图-1 JDK、JRE、JVM关系图-2

JDK、JRE、JVM的关系图

  • JDK(Java Development Kit): JDK 是Java 开发工具包,是整个Java 的核心,包括了Java 运行环境JRE、Java 工具和Java 基础类库。常用的工具包有javac(编译代码)、java、jar (打包代码)、javap(反编译<反汇编>)等
  • JRE(Java Runtime Environment): JRE 是Java 运行环境,它除了包含JVM 之外,还提供了很多的类库(就是我们常说的jar),JVM 标准加上实现的一大堆基础类库,就组成了JRE(Java运行环境)。
  • JVM(Java Virtual Machine): JVM 就是我们所说的Java 虚拟机。JVM 只是一个翻译,把Class 翻译成机器识别的代码,但是需要注意,JVM 不会自己生成代码,需要大家编写代码,同时需要很多依赖类库,这个时候就需要用到JRE。
  • JVM 的作用是:从软件层面屏蔽不同操作系统在底层硬件和指令的不同。这个就是我们在宏观方面对JVM 的一个认识。
  • 同时JVM 是一个虚拟化的操作系统,类似于Linux 或者Windows 的操作系统,只是它架在操作系统上,接收字节码也就是class,把字节码翻译成操作系统上的机器码且进行执行。

# 从跨平台到跨语言

# 跨平台

  • 同一个类可以在不同的操作系统(Windows、Linux、MacOS等)上执行,且效果一样,称之为Java的跨平台性质。(为了实现跨平台型,不同操作系统有对应的JDK 的版本。)

# 跨语言(语言无关性)

  • JVM 只是别字节码,所以JVM 跟语言其实是解耦的,也就没有直接的关联,JVM 运行的不是Java 文件,而是识别javac 编译后的 .class文件,也就是我们的字节码。还有像Groovy 、Kotlin、Scala 等语言,它们其实也是编译成字节码,所以它们也可以在JVM 上面跑,这个就是JVM 的跨语言特征。
  • Java 的跨语言性一定程度上奠定了非常强大的java 语言生态圈。

# JVM的发展

# 常见的JVM

  • Hotspot: 目前使用的最多的Java 虚拟机。在命令行java –version。它会输出你现在使用的虚拟机的名字、版本等信息、执行模式。 JVM信息
  • Jrocket: 原来属于BEA 公司,曾号称世界上最快的JVM,后被Oracle 公司收购,合并于Hotspot。
  • J9: IBM 有自己的java 虚拟机实现,它的名字叫做J9. 主要是用在IBM 产品(IBM WebSphere 和IBM 的AIX 平台上)。
  • TaobaoVM: 只有一定体量、一定规模的厂商才会开发自己的虚拟机,比如淘宝有自己的VM,它实际上是Hotspot 的定制版,专门为淘宝准备的,阿里、天猫都是用的这款虚拟机。
  • LiquidVM: 它是一个针对硬件的虚拟机,它下面是没有操作系统的(不是Linux 也不是windows),下面直接就是硬件,运行效率比较高。
  • zing: 它属于zual 这家公司,非常牛,是一个商业产品,很贵!它的垃圾回收速度非常快(1 毫秒之内),是业界标杆。它的一个垃圾回收的算法后来被Hotspot 吸收才有了现在的ZGC。

# JVM内存区域

# 运行时数据区域的定义

  • Java 虚拟机在执行Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
  • 在JVM 中,JVM 内存主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈等。
  • 同时按照与线程的关系也可以这么划分区域: 线程私有区域:一个线程拥有单独的一份内存区域 线程共享区域:被所有线程共享,且只有一份。
  • 这里还有一个直接内存,这个虽然不是运行时数据区的一部分,但是会被频繁使用。可以理解成没有被虚拟机化的操作系统上的其他内存(比如操作 系统上有8G 内存,被JVM 虚拟化了3G,那么还剩余5G, JVM 是借助一些工具使用这5G 内存的,这个内存部分称之为直接内存) 运行时数据区域

# JAVA 方法的运行与虚拟机栈

# 虚拟机栈

  • 栈的数据结构:先进后出(FILO) 虚拟机栈是线程运行java 方法所需的数据,指令、返回地址。其实在我们实际的代码中,一个线程是可以运行多个方法的。比如: JAVA方法的运行与虚拟机栈 这段代码很简单,就是多层的方法中调用其他方法,从栈帧角度分析:假设线程1运行这段代码时,就会有一个对应的虚拟机栈,同时执行每个方法的时候都会打包成一个栈帧,然后送到虚拟机栈中。具体入栈顺序如下: 虚拟机栈入栈情况 由于栈是先进后出的数据结构,所以,C方法运行完了,C方法出栈,接着B方法运行完了,B方法出栈,接着A方法运行完了,A方法出栈,最后main方法运行完了,main方法这个栈帧就出栈了,综上方法出栈顺序依次是:C、B、A、mian。 这个就是Java 方法运行对虚拟机栈的一个影响。虚拟机栈就是用来存储线程运行方法中的数据的,而每一个方法对应一个栈帧。
  • 虚拟机栈的作用 在JVM 运行过程中存储当前线程运行方法所需的数据,指令、返回地址等。
  • 虚拟机栈的生命周期 虚拟机栈是基于线程的,在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。
  • 栈的大小 虚拟机栈的大小缺省为1M,可用参数 –Xss 调整大小,例如-Xss256k。 设置栈的大小

# 栈帧

  • 在每个Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦方法完成相应的调用,则出栈。
  • 栈帧大体都包含四个区域:局部变量表、操作数栈、动态连接、返回地址。 1、局部变量表 局部变量的表,用来存储局部变量(方法中的变量)的。注意它是一个32 位的长度,主要存放我们的Java 的八大基础数据类型 (byte、short、int、long、float、double、char、boolean),一般32位就可以存放下,如果是64位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的Object 对象,我们只需存放它的引用地址即可。 2、操作数栈 操作数栈,就是以栈(先进后出)来操作任意的java 数据类型。 操作数栈本质上是JVM 执行引擎的一个工作区,也就是方法在执行,才会对操作数栈进行操作,如果代码不不执行,操作数栈其实就是空的,所以一个方法刚刚开始的时候,这个方法的操作数栈就是空的。 3、动态链接 Java语言特性多态(需要结合class 与执行引擎一起来讲)。 4、返回地址
  • 正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)。

# 栈溢出异常(StackOverflowError)

虚拟机栈的内存不是无限大,它有大小限制,默认情况下是1M。 如果我们不断的往虚拟机栈中入栈帧,但是就是不出栈的话,那么这个虚拟机栈就会爆掉。如下: StackOverflowError.png

# 程序计数器

较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。

  • 作用: 程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。
  • 为什么需要程序计数器? 由于Java 是多线程语言,当执行的线程数量超过CPU 核数时,线程之间会根据 时间片轮询 争夺CPU 资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。
  • 程序计数器不记录native方法 因为JVM 是虚拟机,内部有完整的指令与执行的一套流程,所以在运行Java 方法的时候需要使用程序计数器(记录字节码执行的地址或行号),如果 是遇到本地方法(native 方法),这个方法不是JVM 来具体执行,所以JVM的程序计数器不需要记录了,但在操作系统层面也有一个程序计数器,这个会记录本地代码的执行的地址,所以在执行native 方法时,JVM 中程序计数器的值为空(Undefined)。
  • 程序计数器也是JVM 中唯一不会OOM(OutOfMemory)的内存区域。

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

JVM运行时数据区.png

# 本地方法栈

  • 本地方法栈跟Java虚拟机栈的功能类似,区别在于,Java 虚拟机栈服务于Java 函数的调用,本地方法栈服务于本地方法的调用。需要注意的是,本地方法并不是Java 语言实现的,它是由更接近底层的C语言实现的。
  • 由于本地方法栈和虚拟机栈是两个分成相似的区域,所以各个版本的虚拟机在遵循Java虚拟机规范可的前提下以可自由实现(简单点说就是只要你的虚拟机实现里面有这两块内容,这两块内容怎么实现,在哪儿实现都不限制),所以HotSpot 直接把本地方法栈和虚拟机栈合二为一。

# 方法区(Method Area)

  • 方法区主要用来存放已经被虚拟机加载的类的相关信息,包括类信息、静态变量、常量、运行时常量池、字符串常量池等。
  • 方法区是JVM 对内存的“逻辑划分”,在JDK1.7 及之前很多开发者都习惯将方法区称为“永久代”,是因为在HotSpot 虚拟机中,设计人员使用了永久代来实现了Java虚拟机规范的方法区。在JDK1.8 及以后使用了元空间来实现方法区。
  • 方法区与堆空间类似,也是一个共享内存区,所以方法区是线程共享的。假如两个线程都试图访问方法区中的同一个类信息,而这个类还没有装入JVM,那么此时就只允许一个线程去加载它,另一个线程必须等待。

# 元空间(Metaspace)

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

  • 元空间大小参数:

  • jdk1.7 及以前(初始和最大值):-XX:PermSize;-XX:MaxPermSize;

  • jdk1.8 以后(初始和最大值):-XX:MetaspaceSize; -XX:MaxMetaspaceSize

  • jdk1.8 以后大小就只受本机总内存的限制(如果不设置参数的话) Java官方文档参考 (opens new window)

  • Java8 为什么使用元空间替代永久代,这样做有什么好处? 官方给出的解释是: ①移除永久代是为了融合HotSpot JVM 与JRockit VM 而做出的努力,因为JRockit 没有永久代,所以不需要配置永久代。 ②永久代内存经常不够用或发生内存溢出,抛出异常java.lang.OutOfMemoryError: PermGen。这是因为在JDK1.7 版本中,指定的PermGen 区大小为8M,由于PermGen 中类的元数据信息在每次FullGC 的时候都可能被收集,回收率都偏低,成绩很难令人满意; ③还有为PermGen 分配多大的空间很难确定,PermSize 的大小依赖于很多因素,比如,JVM 加载的class 总数、常量池的大小和方法的大小等。

# 运行时常量池(Runtime Constant Pool)

  • 运行时常量池是每一个类或者接口的常量池在运行时的表现形式,它包含了若干不同的常量:从编译期可知的数值字面量到必须* 运行期解析后才能获得的方法或字段引用*。
  • 运行时常量池是方法区的一部分。
  • 运行时常量池相对于Class 常量池的另外一个重要特征是具备动态性:运行期间也可以将新的常量放入池中(Class 常量池在类加载章节会具体讲)。

# 堆

  • 堆是JVM 上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。
  • 堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。堆一般设置成可伸缩的。
  • 随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在Java 中,就叫作GC(Garbage Collection)。

# 对象的分配

一个对象创建的时候,到底是在堆上分配,还是在栈上分配?

  • 这和两个方面有关:对象的类型和在Java 类中存在的位置。我们都知道Java 的对象可以分为基本数据类型和普通对象。首先, 对于普通对象来说,JVM 会首先在堆上创建对象,然后在其他地方使用的其实是它的引用 。比如,把这个引用保存在虚拟机栈的局部变量表中。然后,*对于基本数据类型来说(byte、short、int、long、float、double、char) ,有两种情况,当你在方法体内声明了基本数据类型的对象,它就会在栈上直接分配(这里涉及逃逸分析)。其他情况,都是在堆上分配。

# 堆大小参数

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

# 直接内存(堆外内存)

  • JVM 在运行时,会从操作系统申请大块的堆内存,进行数据的存储;同时还有虚拟机栈、本地方法栈和程序计数器,这块称之为栈区。操作系统剩余的内存也就是堆外内存。 运行时数据区域
  • 它不是虚拟机运行时数据区的一部分,也不是java 虚拟机规范中定义的内存区域;如果使用了NIO,这块区域会被频繁使用,在java 堆内可以用directByteBuffer 对象直接引用并操作。
  • 这块内存不受java 堆大小限制,但受本机总内存的限制,可以通过-XX:MaxDirectMemorySize 来设置(默认与堆内存最大值一样),所以也会出现OOM 异常。

# 小结

**1、直接内存主要是通过DirectByteBuffer 申请的内存,可以使用参数“MaxDirectMemorySize”来限制它的大小。 2、其他堆外内存,主要是指使用了Unsafe 或者其他JNI 手段直接直接申请的内存。 3、由于Java 没有自动管理堆外内存,所以我们要谨慎对堆外内存的使用,因为堆外内存的泄漏是非常严重的,它的排查难度高、影响大,甚至会造成主机的死亡。 ** 4、关于Unsafe说明: Oracle 之前计划在Java 9 中去掉sun.misc.Unsafe API。这里删除sun.misc.Unsafe 的原因之一是使Java 更加安全,并且有替代方案。

#JVM
最近更新: 2025/03/03, 06:23:53
深入理解JVM 内存区域

深入理解JVM 内存区域→

最近更新
01
Kubernetes Helm
04-11
02
Kubernetets Namespace
04-11
03
Kubernetes Ingress
04-11
更多文章>
Theme by Vdoing | Copyright © 2021-2025 光年矩阵科技有限公司 | All Rights Reserved. |
渝ICP备2021888888号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×