分析JVM的组成结构

目录
  • 一、JavaSE体系
  • 二、运行时数据区
  • 三、程序计数器
    • 3.1、什么是程序计数器
    • 3.2、程序计数器有什么特点
    • 3.3、用个例子来说明
  • 四、虚拟机栈
    • 4.1、局部变量表
    • 4.2、操作数据栈
    • 4.3、动态链接
    • 4.4、方法出口
    • 4.5、栈溢出
  • 五、本地方法栈
  • 六、方法区
  • 七、堆
  • 八、运行时常量池
    • 8.1、符号引用
    • 8.2、字面量
    • 8.3、jvm各版本运行时常量池变化
    • 8.4、直接内存

一、JavaSE体系

  • JavaSE,Java 平台标准版,为 Java EE 和 Java ME 提供了基础。
  • JDK:Java 开发工具包,JDK 是 JRE 的超集,包含 JRE 中的所有内容,以及开发程序所需的编译器和调试程序等工具。
  • JRE:Java SE 运行时环境 ,提供库、Java 虚拟机和其他组件来运行用 Java 编程语言编写的程序。主要类库,包括:程序部署发布、用户界面工具类、继承库、其他基础库,语言和工具基础库。
  • JVM:Java 虚拟机,负责 JavaSE 平台的硬件和操作系统无关性、编译执行代码(字节码)和平台安全性。

二、运行时数据区

  • 线程私有:程序计数器、虚拟机栈、本地方法栈。
  • 线程共享:堆、方法区。

三、程序计数器

3.1、什么是程序计数器

程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 -- 摘自《深入理解Java虚拟机》

3.2、程序计数器有什么特点

  • 程序计数器会随着线程的启动而创建,各线程之间独立存储,互不影响。
  • 当前线程执行的字节码的行号指示器。
  • 如果线程正在执行的是一个 Java 方法,则指明当前线程执行的代字节码行数。
  • 如果正在执行的是 Natvie 方法(本地方法),这个计数器值则为空(Undefined)。
  • 占用较小的内存空间,此内存区域是唯一一个不会出现 OutOfMemoryError(内存溢出) 情况的区域。

3.3、用个例子来说明

请无视我文章中取得类名,为了方便实验演示,命名怎么快怎么来。

public class Jvm1 {

    public int test(){
        int a = 100;
        int b = 200;
        return a + b;
    }
}

这样一个类, javac Jvm1.java,编译成Jvm1.class文件。

再使用 javap 反汇编工具javap -c Jvm1.class看下.class文件中数据格式。

这个就是前面提到的 当前线程执行的字节码的行号,而程序计数器则记录的这个数字。

  • 当然这也解释了程序计数器不存在 OutOfMemoryError 的原因,因为它记录的只是数字,占用空间少。
  • 同时也解释了为什么执行的是一个 Java 方法时,则指明当前线程执行的代字节码行数。
  • 而执行 native方法 时程序计数器为 Undefined,因为 native方法 是大多是通过C实现并未编译成需要执行的字节码指令,所以在计数器中当然是空。

四、虚拟机栈

  • 栈有什么特点? 先进后出。
  • 虚拟机栈是每个线程私有的,线程在运行时,在执行每个方法的时候都会打包成一个 栈帧,存储了 局部变量表,操作数据栈,动态链接,方法出口等信息,然后放入栈。每个时刻正在执行的当前方法就是虚拟机栈顶的栈桢。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程。
  • 栈的大小缺省为 1M,可用参数 –Xss 调整大小,例如-Xss256k。

一个例子来看看执行 每个方法入栈出栈 的过程。

public class Jvm2 {

    public static void main(String[] args) {
        A();
    }

    public static void A() {
        System.out.println("A开始");
        // 此处省略100行代码
        B(); // 调用B方法
        System.out.println("A结束");
    }

    public static void B() {
        System.out.println("B开始");
        // 此处省略100行代码
        C(); // 调用B方法
        System.out.println("B结束");
    }

    public static void C() {
        System.out.println("C开始");
        // 此处省略100行代码
        System.out.println("C结束");
    }
}

输出:

A开始

B开始

C开始

C结束

B结束

A结束

4.1、局部变量表

  • 顾名思义就是局部变量的表,用于存放我们的局部变量的。
  • 主要存放我们的 Java 的八大基础数据类型,如果是局部的一些对象,比如我们的 Object 对象,我们只需要存放它的一个引用地址即可。(基本数据类型、对象引用、returnAddress 类型)。

4.2、操作数据栈

  • 存放我们方法执行的操作数的,它就是一个栈,先进后出的栈结构。
  • 操作数栈,就是用来操作的,操作的的元素可以是任意的 java 数据类型。
  • 所以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的,操作数栈运行方法是会一直运行入栈/出栈的操作。

数据重叠优化

虚拟机概念模型中每二个栈帧都是相互独立的,但在实际应用是我们知道一个方法调用另一个方法时,往往存在参数传递,这种做法在虚拟机实现过程中会做一些优化,具体做法如下:令两个栈帧出现一部分重叠。让下面栈帧的一部分操作数栈与上面栈帧的部分局部变量表重叠在一起,进行方法调用时就可以共用一部分数据,无须进行额外的参数复制传递。

4.3、动态链接

需要类加载、运行时才能确定具体的方法。

栈帧中会持有一个引用(符号引用),该引用指向某个具体方法。

符号引用是一个地址位置的代号,在编译的时候我们是不知道某个方法在运行的时候是放到哪里的,这时我用代号 com/enjoy/pojo/User.Say:()V 指代某个类的方法,将来可以把符号引用转换成直接引用进行真实的调用。用符号引用转化成直接引用的解析时机,把解析分为两大类:

  • 静态解析:符号引用在类加载阶段或者第一次使用的时候就直接转换成直接引用。
  • 动态连接:符号引用在每次运行期间转换为直接引用,即每次运行都重新转换。

4.4、方法出口

1、正常返回(调用程序计数器中的地址作为返回)三步曲

  • 恢复上层方法的局部变量表和操作数栈
  • 把返回值(如果有的话)压入调用者栈帧的操作数栈中
  • 调整 PC 计数器的值以指向方法调用指令后面的一条指令

2、异常返回

指方法执行过程中遇到异常,并且这个异常在方法体内部没有得到处理,导致方法退出

4.5、栈溢出

  • java.lang.StackOverflowError:一般的方法调用是很难出现的,如果出现了要考虑是否有 无限递归 ,虚拟机栈带给我们的启示:方法的执行因为要打包成栈桢,所以天生要比实现同样功能的循环慢,所以树的遍历算法中:递归和非递归(循环来实现)都有存在的意义。递归代码简洁,非递归代码复杂但是速度较快。
  • OutOfMemoryError:不断建立线程(一般演示不出,演示出来机器也死了)。

五、本地方法栈

  • 本地方法栈和虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。
  • 虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
  • 本地方法栈 native 方法通过 JNI 调用到了底层的 C/C++(c/c++可以触发汇编语言,然后驱动硬件)。
  • 当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法。

六、方法区

主要存储类信息、常量池、静态变量、即时编译期编译后的代码等数据。

永久代和元空间:

方法区在 jdk1.7 及其之前又背称为永久代,jdk1.8 又被称为元空间,怎么理解呢?

1.jdk1.8移除了永久代,新增了元空间。

2.可以理解为方法区是一个规范,但是具体怎么实现要看具体的jvm怎么实现。

3.就类似于提供了一个接口方法(规范),只要实现了这个接口的类,那么就要去实现里面接口方法(具体实现就是各种版本jvm之间和版本之间的差异了)。

4.各种版本jvm 。

  • HotSpot VM(SUN) 以前使用范围最广的Java虚拟机。
  • JRockit VM(BEA) 号称世界上最快的JVM 。
  • Dalvik VM(Google) google自己开发的。
  • HotSPont VM(ORACLE) 目前以前使用范围最广的Java虚拟机。

5.版本差异(jdk1.7, jdk1.8) 。

参数设置:

  • jdk1.7 及以前:-XX:PermSize;-XX:MaxPermSize;
  • jdk1.8 以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
  • jdk1.8 以后大小就只受本机总内存的限制

七、堆

  • 几乎所有对象都分配在堆内存,也是垃圾回收发生的主要区域。
  • 堆内存由多个线程共享。堆内存随着JVM启动而创建。

参数设置:

-Xms:堆的最小值

-Xmx:堆的最大值

-Xmn:新生代的大小

-XX:NewSize;新生代最小值

-XX:MaxNewSize:新生代最大值

八、运行时常量池

8.1、符号引用

  • 一个 java 类(假设为 People 类)被编译成一个 class 文件时,如果 People 类引用了 Tool 类,但是在编译时 People 类并不知道引用类的实际内存地址,因此只能使用符号引用来代替。
  • 而在类装载器装载 People 类时,此时可以通过虚拟机获取 Tool 类的实际内存地址,因此便可以既将符号 org.simple.Tool 替换为 Tool 类的实际内存地址,及直接引用地址。
  • 即在编译时用符号引用来代替引用类,在加载时再通过虚拟机获取该引用类的实际地址。
  • 以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局是无关的,引用的目标不一定已经加载到内存中。

8.2、字面量

  • 文本字符串 String a = "abc",这个 abc 就是字面量。
  • 八种基本类型 int a = 1; 这个 1 就是字面量。
  • 声明为 final 的常量。

8.3、jvm各版本运行时常量池变化

  • 运行时常量池:Class 文件中的常量池(编译器生成的各种字面量和符号引用)会在类加载后被放入这个区域。
  • JDK1.6:运行时常量池在方法区(永久代)中。
  • JDK1.7:运行时常量池在堆中。
  • JDK1.8:去永久代:使用元空间(空间大小只受制于机器的内存)替代永久代。

8.4、直接内存

内存对象分配在JVM中堆以外的内存,也可以称为直接内存,这些内存直接受操作系统管理(而不是JVM),这样做的好处是能够在一定程度上减少垃圾回收对应用程序造成的影响。

  • 使用 Native 函数库直接分配堆外内存(NIO)。
  • 并不是 JVM 运行时数据区域的一部分,但是会被频繁使用(可以通过-XX:MaxDirectMemorySize 来设置(默认与堆内存最大值一样,也会出现 OOM 异常)。
  • 避免了在 Java 堆和 Native 堆中来回复制数据,能够提高效率。

以上就是分析JVM的组成结构的详细内容,更多关于JVM组成结构的资料请关注我们其它相关文章!

(0)

相关推荐

  • JVM内存结构:程序计数器、虚拟机栈、本地方法栈

    目录 一.JVM 入门介绍 JVM 定义 JVM 优势 JVM JRE JDK的比较 学习步骤 二.内存结构 整体架构 1.程序计数器(寄存器) 1.1 作用 1.2 特点 2.虚拟机栈 2.1 定义 2.2 演示 2.3 面试问题辨析 2.4 内存溢出 2.5 线程运行诊断 3.本地方法栈 4.总结 一.JVM 入门介绍 JVM 定义 Java Virtual Machine,JAVA程序的运行环境(JAVA二进制字节码的运行环境) JVM 优势 一次编写,到处运行 自动内存管理,垃圾回收机制

  • 了解Java虚拟机JVM的基本结构及JVM的内存溢出方式

    JVM内部结构图 Java虚拟机主要分为五个区域:方法区.堆.Java栈.PC寄存器.本地方法栈.下面 来看一些关于JVM结构的重要问题. 1.哪些区域是共享的?哪些是私有的? Java栈.本地方法栈.程序计数器是随用户线程的启动和结束而建立和销毁的, 每个线程都有独立的这些区域.而方法区.堆是被整个JVM进程中的所有线程共享的. 2.方法区保存什么?会被回收吗? 方法区不是只保存的方法信息和代码,同时在一块叫做运行时常量池的子区域还 保存了Class文件中常量表中的各种符号引用,以及翻译出来的

  • JVM内存结构相关知识解析

    最近在看< JAVA并发编程实践 >这本书,里面涉及到了 Java 内存模型,通过 Java 内存模型顺理成章的来到的 JVM 内存结构,关于 JVM 内存结构的认知还停留在上大学那会的课堂上,一直没有系统的学习这一块的知识,所以这一次我把< 深入理解Java虚拟机JVM高级特性与最佳实践 >.< Java虚拟机规范 Java SE 8版 >这两本书中关于 JVM 内存结构的部分都看了一遍,算是对 JVM 内存结构有了新的认识.JVM 内存结构是指:Java 虚拟机定义

  • 深入解析JVM之内存结构及字符串常量池(推荐)

    前言 Java作为一种平台无关性的语言,其主要依靠于Java虚拟机--JVM,我们写好的代码会被编译成class文件,再由JVM进行加载.解析.执行,而JVM有统一的规范,所以我们不需要像C++那样需要程序员自己关注平台,大大方便了我们的开发.另外,能够运行在JVM上的并只有Java,只要能够编译生成合乎规范的class文件的语言都是可以跑在JVM上的.而作为一名Java开发,JVM是我们必须要学习了解的基础,也是通向高级及更高层次的必修课:但JVM的体系非常庞大,且术语非常多,所以初学者对此非

  • JVM入门之内存结构(堆、方法区)

    目录 1.堆 1.1 定义 1.2 堆的作用 1.3 特点 1.4 堆内存溢出 1.5 堆内存诊断 2.方法区 2.1 结构(1.6 对比 1.8) 2.2 内存溢出 2.3 常量池 2.4 运行时常量池 2.5 常量池与串池的关系 2.6 StringTable的位置 2.7 StringTable 垃圾回收 2.8 方法区的垃圾回收 3.直接内存 释放原理 1.堆 1.1 定义 是Java内存区域中一块用来存放对象实例的区域[几乎所有的对象实例都在这里分配内存] 通过new关键字创建的对象都

  • JVM 体系结构详解

    JVM 是一种抽象的计算机,基于堆栈架构,它有自己的指令集和内存管理,是 Java 跨平台的依据,JVM解释执行字节码,或将字节码编译成本地代码执行.Java 虚拟机体系结构如下: Class File Class File 是平台无关的二进制文件,包含着能被JVM执行的字节码,其中多字节采用大端序,字符使用一种改进的UTF-8编码.Class文件精确的描述了一个类或接口的信息,其中包括: 常量池:数值和字符串字面常量,元数据如类名.方法名称.参数,以及各种符号引用 方法的字节码指令,参数个数,

  • JVM内存结构划分实例解析

    这篇文章主要介绍了JVM内存结构划分实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 数据区域划分 运行时内存区域划分:程序计数器.虚拟机栈.本地方法栈.堆.方法区 程序计数器 线程私有 通过寄存器实现 不会存在运行溢出 当前线程所执行的行号指示器,记住下一条JVM指令的执行地址 虚拟机栈 垃圾回收不涉及栈内存 栈内存是线程私有的,可以理解为线程运行需要的内存空间 栈由栈帧组成,每个栈帧代表一个方法执行时需要的内存(参数,局部变量,返回地

  • 深入理解JVM之Class类文件结构详解

    本文实例讲述了深入理解JVM之Class类文件结构.分享给大家供大家参考,具体如下: 概述 我们平时在DOS界面中往往需要运行先运行javac命令,这个命令的直接结果就是产生相应的class文件,然后基于这个class文件才可以真正运行程序得到结果.自然.这是Java虚拟机的功劳,那么是不是Java虚拟机只能编译.java的源文件呢?答案是否定的.时至今日,Java虚拟机已经实现了语言无关性的特点.而实现语言无关性的基础是虚拟机和字节码的存储格式,Java虚拟机已经不和包括Java语言在内的任何

  • 分析JVM的组成结构

    目录 一.JavaSE体系 二.运行时数据区 三.程序计数器 3.1.什么是程序计数器 3.2.程序计数器有什么特点 3.3.用个例子来说明 四.虚拟机栈 4.1.局部变量表 4.2.操作数据栈 4.3.动态链接 4.4.方法出口 4.5.栈溢出 五.本地方法栈 六.方法区 七.堆 八.运行时常量池 8.1.符号引用 8.2.字面量 8.3.jvm各版本运行时常量池变化 8.4.直接内存 一.JavaSE体系 JavaSE,Java 平台标准版,为 Java EE 和 Java ME 提供了基础

  • 分析JVM的执行子系统

    目录 一.Class类文件结构 1.1.JVM的平台无关性 1.2.Class类文件 二.类的加载机制 2.1.加载 2.2.验证 2.3.准备阶段 2.4.解析阶段 2.5.初始化阶段 三.类加载器 3.1.双亲委派模型 3.2.Tomcat是怎么保证两个应用相同名称类的隔离性 一.Class类文件结构 1.1.JVM的平台无关性 与平台无关性是建立在操作系统上,虚拟机厂商提供了许多可以运行在各种不同平台的虚拟机,它们都可以载入和执行字节码,从而实现程序的一次编写,到处运行. 各种不同平台的虚

  • 高分面试分析jvm如何实现多态

    目录 这样说,六十分 这样说,七八十分 昨天就有一个小伙伴被一道面试题虐了,我也给了他一定深度的答案.但是我觉得不够,我觉得应该让小伙伴们像我一样,答题能答出惊喜感,于是就有了这篇文章.我会从Java层面到Hotshot源码层面再到C++层面,完整分析这个问题. 这道面试题在好一些的互联网公司,尤其是一二线,问到的概率非常大,建议小伙伴们把这篇文章吃透. 这样说,六十分 多态是面向对象的三大特性之一,我个人认为,当时设计OOP机制的时候,能够想到多态的人,真特么太牛叉了. 多态理论第一次有了具体

  • 详细分析JVM类加载机制

    目录 前言 1. jvm 的组成 2. 类加载 1. 加载 2. 链接 3. 初始化 3. 类加载器 引导类加载器(启动类加载器) 扩展类加载器 应用程序类加载器 4. 双亲委派机制 5. 类的主动/被动使用 结语 前言 ladies and gentleman , 你们好 ,我是羡羡 , 这节我们进入jvm的学习 , 我们知道 , jvm是java虚拟机, java代码的执行与 jvm 息息相关, 接下来我们来依次介绍 , 首先这节先来介绍 jvm 中的类加载部分 1. jvm 的组成 jvm

  • 分析JVM源码之Thread.interrupt系统级别线程打断

    目录 一.interrupt的使用特点 二.jvm层面上interrupt方法的本质 三.ParkEvent对象的本质 四.Park()对象的本质 五.利用jni实现一个可以被打断的MyThread类 六.总结 一.interrupt的使用特点 我们先看2个线程打断的示例 首先是可打断的情况: @Test public void interruptedTest() throws InterruptedException { Thread sleep = new Thread(() -> { tr

  • 深入理解Java虚拟机 JVM 内存结构

    目录 前言 JVM是什么 JVM内存结构概览 运行时数据区 程序计数器 Java虚拟机栈 本地方法栈 方法区 运行时常量池 Java堆 直接内存 前言 JVM是Java中比较难理解和掌握的一部分,也是面试中被问的比较多的,掌握好JVM底层原理有助于我们在开发中写出效率更高的代码,可以让我们面对OutOfMemoryError时不再一脸懵逼,可以用掌握的JVM知识去查找分析问题.去进行JVM的调优.去让我们的应用程序可以支持更高的并发量等......总之一句话,学好JVM很重要! JVM是什么 J

  • C#中结构(struct)的部分初始化和完全初始化实例分析

    本文实例分析了C#中结构(struct)的部分初始化和完全初始化,分享给大家供大家参考.具体分析如下: 假设有这样一个值类型struct,如下所示: public struct Size { public int Length; public int Width; public int Area() { return Length*Width; } } 一.客户端,给所有struct字段初始化后调用方法 class Program { static void Main(string[] args

  • C++结构体用法实例分析

    本文实例讲述了C++结构体用法.分享给大家供大家参考.具体分析如下: C++结构体提供了比C结构体更多的功能,如默认构造函数,复制构造函数,运算符重载,这些功能使得结构体对象能够方便的传值. 比如,我定义一个简单的结构体,然后将其作为vector元素类型,要使用的话,就需要实现上述三个函数,否则就只能用指针了. 复制代码 代码如下: #include <iostream>  #include <vector>   using namespace std;  struct ST  {

随机推荐