0%

Windows Terminal美化与基本配置,包括Powershell无法运行脚本的问题解决、支持命令行自动补全、自定义主题……

阅读全文 »

一、程序计数器

  • def 一块较小的内存空间,可看作当前线程所执行字节码的行号指示器

  • 作用 用来存储指向下一条指令的地址,供执行引擎读取

    • 直观的说法是,程序计数器用于记录当前代码执行的位置
    • 类比物理寄存器的 PC,JVM 中的 PC 寄存器是它的抽象模拟

1. 特点

  • 线程私有,生命周期和线程一致

    • 每个线程都有其程序计数器,互不影响、独立存储
  • 程序控制流的指示器:分支、循环、跳转、异常处理、线程恢复等功能都依赖其完成

  • 根据线程正在执行方法的不同,其记录的值不同

    • 若为 Java 方法,记录 JVM 字节码指令的地址
    • 若为 Native 方法,置空(undefined)
  • 唯一没有规定任何 OutOfMemoryError 的区域

2. 相关问题

 点击折叠

Q1:为什么程序计数器被设定为线程私有的

A:因为 Java 中的多线程实际上是通过各线程之间轮流切换、分配 CPU 处理时间来实现的,任一确定的时刻,CPU 只执行一条线程中的指令;为了在线程切换后能够恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,其内存区域是线程私有的。

Q2:为什么使用 PC 寄存器记录当前线程的执行地址

A:字节码解释器(interpreter)需要通过改变程序计数器的值,来明确下一条应该执行的字节码指令

二、虚拟机栈

  • def 描述 Java 方法执行时的线程内存模型:每个线程在创建的时候都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的 Java 方法调用

  • 作用 主管 Java 程序的运行

1. 特点

  • 线程私有,生命周期和线程一致

  • JVM 直接对虚拟机栈进行的操作只有两个:方法执行入栈,执行结束出栈

  • 可由线程固定或动态扩展内存大小,不存在垃圾回收问题

    • 对固定大小栈,若线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量,JVM 将抛出 StackOverflowError 异常
    • 对动态扩展栈,若没有足够的内存,JVM 将抛出 OutOfMemoryError 异常

2. 栈帧的构成

  • 局部变量表(Local Variables):存储方法参数和定义在方法体内的局部变量,如基本数据类型、对象引用等

    • 基本存储单元:局部变量槽(slot),32 位以内的类型占用一个 Slot,64 位的类型占用两个连续的 Slot
  • 操作数栈(Operand Stack):在方法执行过程中,根据字节码指令,往操作数栈中写入数据或提取数据,即入栈(push)、出栈(pop)

    • 主要用于保存计算的中间结果,并作为计算过程中变量的临时存储空间
  • 动态链接(Dynamic Linking):每个栈帧内部均包含一个可指向当前方法所在类的运行时常量池,该栈帧所属方法的引用,以实现动态链接

  • 方法返回地址(Return Address):存放调用该方法的程序计数器的值

    • 正常返回出口:返回值传递给上层的方法调用者
    • 异常返回出口:不产生任何返回值
  • 附加信息

3. 相关问题

 点击折叠

Q1:栈帧的创建 / 销毁时机

A:当方法被执行时,JVM 会同步创建一个栈帧;方法从调用到执行结束的过程,对应着栈帧在虚拟机栈从入栈到出栈的过程。

Q2:如何访问局部变量表中一个 64bit 的局部变量值

A:JVM 会为局部变量表中的每一个局部变量槽都分配一个访问索引,因为 64bit 的局部变量值占据两个 slot 空间,故应使用该局部变量的前一个索引访问(不能采用任何方式单独访问其中的某一个 Slot)

三、本地方法栈

  • 作用 主管本地方法(native method)的调用

1. 特点

  • 线程私有,生命周期和线程一致

  • 可由线程固定或动态扩展内存大小

    • 对固定大小栈,若线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量,JVM 将抛出 StackOverflowError 异常
    • 对动态扩展栈,若没有足够的内存,JVM 将抛出 OutOfMemoryError 异常
  • optional:不是所有的 JVM 都支持

在 Hotspot JVM 中,本地方法栈和虚拟机栈是合二为一的

2. 本地方法接口,JNI

  • def 一个 Java 调用本地方法的接口

  • 作用 提高程序编译效率,并适用于与外部环境交互

本地方法使用 C 实现

四、堆

  • 作用 存放对象实例,几乎所有对象实例和数据均在此分配内存

  • 注:JDK1.7 后,字符串常量池从方法区移动到了堆中

1. 特点

  • 所有线程共享

  • 是虚拟机所管理的最大一块内存

五、方法区

  • 作用 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器的代码缓存等数据

1. 特点

  • 所有线程共享

2. 方法区的构成

  • 类信息表

  • 运行时常量池



参考资料:

  1. 《深入理解 Java 虚拟机(第 3 版)》,周志明著

  2. JVM 基础 - JVM 内存结构 | Java 全栈知识体系 (pdai.tech)

  3. Java JVM 虚拟机 已完结(IDEA 2021 版本)4K 蓝光画质 全程劝退

JNI

JNI 全称 Java 动态接口(Java Native Interface),专门于为其他语言提供自己的规范

  • 作用是融合不同的编程语言为 Java 所用,初衷是融合 C/C++ 程序

步骤说明

  • Java 交予 JVM 编译得到二进制文件(.class),再编译生成 C++ 头文件(.h)

  • 在相应.cpp 文件中引入

  • 通过 DLL 项目生成 64 bit 动态链接库(.dll)

  • 将动态链接库存放于 % JAVA_HOME% 路径,供 API 调用

具体操作

JavaToCpp

  • 编写 Java 类,声明 sayHello () 方法

    • 一个 Native Method 就是一个 Java 调用非 Java 代码的接口,就如 C++ 中可用 extern 告知编译器调用一个 C 函数
1
2
3
4
5
6
7
public class Test {
public native void sayHello();

static {
System.loadLibrary("CppToJava");
}
}
  • 生成 JNI 头文件

1
javah -jni -classpath ./ Test

CppToJava

  • 创建 DLL 项目,引入该头文件

  • 实现 sayHello () 本地方法

1
2
3
4
5
6
7
#include <iostream>

#include "Test.h"

JNIEXPORT void JNICALL Java_Test_sayHello(JNIEnv*, jobject) {
std::cout << "im from cpp" << std::endl;
}
  • 指定 x64 平台,生成解决方案

Main

  • 需要先将生成的动态链接库放至 % JAVA_HOME% 路径

  • 调用

1
2
3
4
5
public static void main(String[] args) {
// 当前path,如本机位置D:\Java\jdk1.8\bin
// System.out.println(System.getProperty("java.library.path"));
new Test().sayHello();
}

Q&A

Q:出现以上报错?

A:这是因为生成的 DLL 是 32 位的不支持 64 位的平台(jdk 环境),应在生成时指定为 64 位 DLL