jvm(3)-运行时数据区、指令集

2024-05-02 09:08
文章标签 java 数据 jvm 运行 指令集

本文主要是介绍jvm(3)-运行时数据区、指令集,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. Runtime Data Area and Instruction Set(运行时数据区和指令集)

1.1 jvm运行时数据区

分为以下几部分:

  1. jvm stacks ,就是我们常说的栈,栈里面包含frame(窗口,也叫栈帧)
  2. Program Counter,简称PC
  3. method area 方法区,包括常量池。
  4. native method stacks,本地方法栈。
  5. Direct, 直接内存
  6. heap,堆

在这里插入图片描述
详细了解一下这几个区:

1.2 Program Counter 简称PC

  1. PC计数器,用来存放指令。每一个线程都一个自己的PC计数器
  2. 为什么每一个线程都有自己的PC计数器呢?
    便于线程切换。
    到底是什么意思呢?
    A线程执行到某个位置后,cpu切换到B线程,当B线程执行完,回到A线程,此时,A线程需要知道自己执行到哪里了。得记住自己执行到那个指令了,切换回来之后还需要继续执行。

虚拟机的运行,类似于这样的循环:

while( not end(指令不结束) ) {
取PC中的位置,找到对应位置的指令;
执行该指令;
PC ++;
}

1.3 jvm stacks 栈

每一个线程对应一个jvm栈,每一个方法对应一个栈帧

Frame- 每个方法对应一个栈帧。

Frame的构成如下图:
包括四部分:Local Variables Operand Stacks. dynamic liking return address
在这里插入图片描述
详细了解一下:

1.3.1 . Local Variable Table(局部变量表)

局部变量表,指这个方法中定义的局部变量,下面看一下静态方法中的局部变量表和成员方法中的局部变量表

  1. 如果方法是static方法,则有几个局部变量就是几个局部变量。
    原始代码:
package com.tzw.classMode;
public class MyTest02 {public static void main(String[] args) {MyTest02.hi("hi");}public static void hi(String s){System.out.println(s);}
}

通过jclasslib插件查看指令

  1. 有两个方法,分别是main方法和code方法

  2. 这连个方法都是static方法,所以LacalVariableTable(局部变量表)中只有一个参数s对应常量池中的 #21。

  3. 所以在静态方法中能看到几个变量就是几个变量
    在这里插入图片描述

  4. 如果方法是普通方法,则局部变量表中0号位置的变量默认就是this。
    原始代码:

package com.tzw.classMode;public class MyTest01 {public static void main(String[] args) {MyTest01 myTest01 = new MyTest01();myTest01.hello("hello");}public void hello(String str){System.out.println(str);}
}

通过jclasslib插件查看指令:

  1. 有两个方法,分别是main方法和hello方法
  2. hello方法是成员方法都,所以LacalVariableTable(局部变量表)中看似只有一个参数,实则有两个参数,表的第一个位置放的是this。
  3. 所以在成员方法中能看到几个变量就是几个变量+1个参数,因为在成员方法中,默认在方法中放了一个this的参数。
    在这里插入图片描述

1.3.2. Operand Stack(操作栈)

这个理解为一个栈帧中的小栈,叫做操作栈,用来做数据操作的地方。
每个线程都有一个栈,每个栈有多个栈帧,每个栈帧里都有一个Operand stack(操作栈)

根据不同的指令在栈里做相关的处理。我们先需要了解一下指令集。详细见第2章指令集。

1.3.3. Dynamic Linking (动态链接)

指向常量池的的linking。链接

1.3.4. return address (返回地址)

a() -> b(),方法a调用了方法b, b方法的返回值放在什么地方。
就是说一个方法就是一个栈帧,那么这个方法有返回值,这个返回值的地址就是这个return address。
​​​​ ​​​​​​​

1.4 native method stacks 本地方法栈。

调用c c++的方法。

1.5 direct Memory 直接内存

  1. 不归属jvm管理,归属操作系统管理
    在之前,当有io数据传递过来,会存储到操作系统的内核空间内存中,当jvm要使用的时候,需要拷贝一份数据到jvm中。有拷贝的过程
    现在有了直接内存,直接内存直接去读取os内核的内存,不需要在去拷贝。零拷贝
  2. JVM可以直接访问的内核空间的内存 (OS 管理的内存)
  3. NIO , 提高效率,实现zero copy

1.6 method area 方法区

存储每一个class的结构,常量池等。method area是一个概念上的东西。

  1. Prem Space(<1.8),jdk1.8之前
    在1.8之前方法区的具体实现就叫Prem generation。可以理解为1.8之前方法区就叫做Prem generation
    字符串常量位于Permgeneration, FGC不会清理, 大小启动的时候指定,不能变。
  2. Meta Space(>1.8),jdk1.8之后
    在1.8之后方法区的具体实现就叫Meta Space,可以理解为1.8之前方法区就叫做MateSpace。
    字符串常量位于堆,会触发FGC清理,不设定大小的话,最大就是物理内存

如何证明: 1.7 字符串常量位于Perm,而1.8位于Heap中
提示:结合GC, 一直创建字符串常量,观察堆,和Metaspace

jdk1.8之前,jvm的对内存分为新生代+老年代+持久代。而持久代就是perm generation。方法区(Method
Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。很多人都更愿意把方法区称为“永久代”(Permanent
Generation)。

jdk1.7之前永久代包含字符串常量池,静态变量等。而在jdk1.7的Hotspot中,已经将字符串常量池,静态变量等移动heap堆中了,不在永久代中了。

jdk1.8中,永久代已经不存在,存储的类信息、编译后的代码数据等已经移动到了元空间(MetaSpace)中,元空间并没有处于堆内存上,而是直接占用的本地内存(NativeMemory)。而字符串常量池,静态变量等移动heap堆中。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
  -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
  除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
  -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
  -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

简单理解就是:
在Java7之前,HotSpot虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。而在Java8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做元空间。

1.7. Heap 堆

gc 补充
​​

2. 指令集

指令集的分类:

  1. 基于寄存器的指令集
  2. 基于栈的指令集
    Hotspot中的Local Variable Table = JVM中的寄存器

2.1 创建对象的指令

  1. 先来分析一个创建对象的指令集,仅分析main方法中MyTest01 myTest01 = new MyTest01();这一个指令
    源代码:MyTest01 myTest01 = new MyTest01();
package com.tzw.classMode;public class MyTest01 {public static void main(String[] args) {MyTest01 myTest01 = new MyTest01();myTest01.hello("hello");}public void hello(String str){System.out.println(str);}
}
  1. 这个类经过翻译的字节码指令集如下。
    前四行就是MyTest01 myTest01 = new MyTest01();操作。
 0 new #2 <com/tzw/classMode/MyTest01>3 dup4 invokespecial #3 <com/tzw/classMode/MyTest01.<init> : ()V>7 astore_18 aload_19 ldc #4 <hello>
11 invokevirtual #5 <com/tzw/classMode/MyTest01.hello : (Ljava/lang/String;)V>
14 return
  1. 分析:(查看jvm指令集文档https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-6.html#jvms-6.5.dup)
    (1) new 。 new一个空间,将地址存入opeand stacks。类此时赋值为默认值。
    (2)dup 。文档意思:Duplicate the top operand stack value。 在opeand stacks中在复制一份地址。此时opeand中有两个地址。
    (3)invokespecial 调用构造方法,同时将复制的那个地址从operand stack中弹出用来使用,类此时赋值为初始化。
    (4)astore_1 。 将operand stack 中对象空间地址弹出,并赋值带变量值myTest01。这就是为什么我们打印一个对象时打印的是一个地址。
    上面4步完成了MyTest01 myTest01 = new MyTest01();操作。

2.2 继续深入

上述已经完成了对象的创建,但是这个方法还没有完成,我们继续来读指令。
(5)aload_1 ,再次将变量表中第1个位置的变量值那出来压栈。就是将myTest01拿出来压栈。
(6)ldc ,将参数值“hello” 推到常量池中
(7)invokeviirtual ,调用hello()方法。接下来的事就到了另一个方法或者栈帧中。
(8)return 返回。

分析一下hello()方法:hello方法的指令集为

0 getstatic #6 <java/lang/System.out : Ljava/io/PrintStream;>
3 aload_1
4 invokevirtual #7 <java/io/PrintStream.println : (Ljava/lang/String;)V>
7 return

(1)getstatic 调用静态方法System.out
(2) aload_1将变量s的值压栈
(3)invokevirtual,调用成员方法 println。
(4)返回。

2.3 常用指令集

  1. astore_x,将值存储到局部变量表第x个位置的变量名上
  2. aload_x,将第x个位置的变量的值取出压栈。
  3. pop 弹栈
  4. mul 乘
  5. sub 减
  6. invoke系列:
  7. InvokeStatic 调用静态方法
  8. InvokeVirtual 调用成员方法/实例方法

自带多态。什么意思呢?比如说一个类有多个子类。
在调用实例方法时,new的是哪个子类,就是调用这个子类的方法。
首先需要将对象实例化,并赋值给变量。
调用方法,将该变量压栈,然后调用栈里的对象的方法。

  1. InvokeInterface 接口调用方法
    eg:
List list = new ArrayList();
list.add();
  1. InovkeSpecial 构造方法,private 方法,不需要多态的方法等可以直接定位的方法
  2. InvokeDynamic
    JVM最难的指令,lambda表达式或者反射或者其他动态语言scala kotlin,或者CGLib ASM,动态产生的class,会用到的指令

这篇关于jvm(3)-运行时数据区、指令集的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/953899

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定