Java 虚拟机中的内存结构

1 内存结构

1.1 程序计数器

image-20231222142726484

1.1.1 定义

Program Counter Register 程序计数器(寄存器)

作用:是记住下一条 jvm 指令的执行地址

特点:

  • 是线程私有的(每个线程独有自己的一份)
  • 不会存在内存溢出

1.1.2 作用

记住下一条 jvm 指令的执行地址 (0,3,4,5,...)

image-20231222143054213

线程私有的:

每个线程都有一个自己的程序计数器,里面存储了自己线程运行到了哪条指令

image-20231222144028283

1.2 虚拟机栈

image-20231222144244229

1.2.1 定义

Java Virtual Machine Stacks (Java 虚拟机栈)

  • 每个线程运行时所需要的内存,称为虚拟机栈
  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用所占用的内存
  • 每个线程只有一个活动栈帧,对应当前正在执行的那个方法

栈:线程运行需要的内存空间

栈中存储着多个栈帧,每个栈帧对应着一个调用过的方法,栈顶为活动栈帧,是当前正在运行的函数;当一个方法运行完成,这个方法对应的栈帧就会出栈

image-20231222144505865

问题辨析

  1. 垃圾回收是否涉及栈内存?

    不需要,每次方法结束时,栈内存就被回收掉了,不需要等待垃圾回收;垃圾回收是用来回收堆内存中的对象的

  2. 栈内存分配越大越好吗?

    • 可以用 -Xss size 指令设置栈内存的大小
    • Linux & macOS默认 1024 KB,Windows 依赖虚拟内存的大小
  3. 方法内的局部变量是否线程安全?

    • 如果方法内局部变量没有逃离方法的作用访问,是线程私有的,它是线程安全的

      image-20231222145641730

    • 如果是局部变量引用了对象,对象是共有的,并逃离方法的作用范围,需要考虑线程安全

      image-20231222145625818

/*** 局部变量的线程安全问题*/public class d1_local_var_safe {// 多个线程执行这个方法public static void method1(String[] args) {int x = 0;for (int i = 0; i < 5000; i++) {x++;}System.out.println(x);}
}

1.2.2 栈内存溢出

  • 栈帧过多导致栈内存溢出。例如方法的递归调用
image-20231222152256064
  • 栈帧过大导致栈内存溢出

1.2.3 线程运行诊断

案例一:CPU 占用过高

  • 可以用 top 命令定位哪个进程对 CPU 的占用过高

  • 找到进程后,我们想进一步的定位到具体的线程,可以用 ps 命令

    • ps H pid,tid,%cpu 可以把当前所有线程的 pid,tid,cpu的信息展示出来
    • 使用管道过滤出具体的进程号:ps h -eo pid,tid,%cpu | grep 32655
  • jstack 进程id 命令可以列出Java虚拟机中的所有的 Java 线程

    • 根据 ps 命令找到的线程号可以在 jstack 中找到对应的线程,里面给出该线程的状态和问题代码行号

案例二:程序运行很长时间没有结果

可能发生了死锁等等

使用 jstack 命令进行检查

1.3 本地方法栈

image-20231222154054304

我们可以通过本地方法调用一些用C/CPP写的更底层的方法,例如Object类中的clone , hashCode… 方法

1.4 堆

image-20231222154417878

1.4.1 定义

Heap 堆:

  • 通过 new 关键词创建对象会使用堆内存

特点:

  • 他是线程共享的,堆中对象需要考虑线程安全问题
  • 堆中有垃圾回收机制

1.4.2 堆内存溢出

不断的向堆中添加对象,且这些对象无法回收,一段时间后会导致堆内存溢出

1.4.3 堆内存诊断

package com.rainsun.d1_Java_memory_structure;
/*** 堆内存演示*/
public class d2_heap_memory {public static void main(String[] args) throws InterruptedException {System.out.println("1.....");Thread.sleep(30000);byte[] array = new byte[1024 * 1024 * 10]; // 10Mb 空间System.out.println("2.....");Thread.sleep(30000);array = null;System.gc();System.out.println("3.....");Thread.sleep(100000L);}
}

查看堆内存信息:

  • jps 找到当前运行的线程id
  • jhsdb jmap --pid 线程id --heap 输出堆占用信息

1:

image-20231222160549701

2:

image-20231222160605952

3:没有G1 Heap了

  • jconsole 工具

    图形界面的,多功能的监测工具,可以连续监测

1.5 方法区

image-20231222161551426

方法区是线程共享的,存储类相关的信息(方法,构造器,特殊方法,运行时常量池等等)

Chapter 2. The Structure of the Java Virtual Machine (oracle.com)

image-20231222161731756

JDK1.6中的结构:

使用 PermGen 永久代实现

image-20231222161942216

JDK1.8中的结构:

元空间实现,使用操作系统中的本地内存实现:

image-20231222162115139

1.5.4 运行时常量池

  • 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
  • 运行时常量池:
    • 常量池是 *.class 文件中的;
    • 当程序运行的时候,每个被加载的类,它的.class文件中常量池信息就都会放入运行时常量池,运行时常量池保存了所有被加载了的类中的常量池信息
    • 并把里面的符号地址变为真实地址

1.5.5 StringTable

public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "a" + "b";String s4 = s1 + s2;
}

反编译:

  • 常量池中的信息,都会被加载到运行时常量池中,这时 a , b 都是常量池中的符号,还没有变成 Java 字符串对象
  • ldc #2 会把 a 符号变为 “a” 字符串对象,并把 “a” 放入 StringTable 中,如果没有则加入新的,有则不加入新的

s1,s2,s3 字符串对象的产生是发生在字符串常量池的 StringTable 中

s4 字符串对象的产生是通过调用两次 makeConcatWithConstants 方法将常量池中的 “a” ,“b” 进行拼接,在堆中创建了一个新的字符串变量,最后存入临时变量 s4中的。

image-20231222174240646

这是 JDK 21 反编译的结果,JDK 8 不一样,可能是 append.append.toString

StringTable 特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
public static main(String[] args){String s = new String("a") + new String("b");
}

StringTable 中创建了字符串对象 [“a”,“b”]

在堆中产生了字符串对象,因为使用了 new 来创建 “a”,“b” ,“ab”

intern:

将字符串对象放入 StringTable,如果table中有则不会放入,没有则放入,并把 table 中的对象返回

String s2 = s.intern(); // 堆中的 "ab" 被放入了 StringTable中了, 并将table中的ab返回
public class d3_stringTable {// 常量池中的信息,都会被加载到运行时常量池中,这时 a , b 都是常量池中的符号,还没有变成 Java 字符串对象// ldc #2 会把 a 符号变为 "a" 字符串对象,并把 "a" 放入 StringTable 中,如果没有则加入新的,有则不加入新的public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "a" + "b"; //javac在编译期间的优化,变成了 "ab",结果在编译期就已经确定了String s4 = s1 + s2; // 堆中的ab: new String("ab")String s5 = "ab"; // == s3 在table中的abString s6 = s4.intern(); // table中ab已经有了,返回table中的abSystem.out.println(s3 == s4); //falseSystem.out.println(s3 == s5); // trueSystem.out.println(s3 == s6); // trueString x2 = new String("c") + new String("d");String x1 = "cd";x2.intern();  // table中 cd 已经存在了,x2放不进去table,x2还是堆中的cdSystem.out.println(x1 == x2); // false/** 最后两行代码的位置调换:* String x2 = new String("c") + new String("d");* x2.intern(); // table 中 cd 不存在,x2放入table,x2 变成了table中的cd* String x1 = "cd"; // x1 用的是table中的cd* System.out.println(x1 == x2); // true*/}
}

1.5.6 StringTable 位置

image-20231222200203414

1.6 中的StringTable在PermGen永久代中实现,它的回收时机比较晚,容易造成永久代的内存空间不足

在1.8中就将StringTable放入堆Heap中实现,它的垃圾回收时机触发比较早,可以更快回收不用的字符串空间

image-20231222162115139

1.5.7 StringTable 垃圾回收

那些没有用到的字符串常量会被垃圾回收

package com.rainsun.d1_Java_memory_structure;/*** 演示 StringTable垃圾回收* -Xmx10m* -XX:+PrintStringTableStatistics : 打印 StringTable 信息* -XX:+PrintGCDetails -verbose:gc :打印垃圾回收信息*/
public class d4_stringTable_GC {public static void main(String[] args) {int i = 0;try {for (int j = 0; j < 100000; j++) {String.valueOf(j).intern();i++;}}catch (Throwable e){e.printStackTrace();}finally {System.out.println(i);}}
}

image-20231222202328823

1.5.8 StringTable 调优

  1. -XX:StringTableSize=桶个数 可以设置String Table 中桶的个数,类似于一个哈希表,当表越大,每个桶中链表的长度就越小,查找的速度就越快
  2. 考虑将字符串对象是否入池
    • 如果字符串大量重复可以让字符串入池,减少重复字符串存在在heap中

1.6 使用操作系统中的直接内存

1.6.1 Direct Memory

  • 常见于 NIO 操作,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受 JVM 内存回收管理

  • 传统的阻塞式 IO 读取数据的方式:

CPU从Java的用户态转为系统的内核态,这样可以读取磁盘中的文件到系统的缓冲区,但是系统的缓冲区 Java 无法获取,所以还需要将系统缓冲区中的数据传给Java缓冲区

image-20231222203852847

  • 使用直接内存进行文件读取:

会产生一块 direct memory的区域,操作系统和Java都可以读取

image-20231222204121083

内存溢出问题

当direct memory占用太大会抛出内存溢出异常

1.6.2 分配和回收原理

  • 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法

    package com.rainsun.d1_Java_memory_structure;import sun.misc.Unsafe;import java.io.IOException;
    import java.lang.reflect.Field;public class d5_direct_memory {static int _1Gb = 1024*1024*1024;public static void main(String[] args) throws IOException {Unsafe unsafe = getUnsafe();// 分配内存long base = unsafe.allocateMemory(_1Gb);unsafe.setMemory(base, _1Gb, (byte) 0);System.in.read();// 释放内存unsafe.freeMemory(base);System.in.read();}public static Unsafe getUnsafe() {try {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);return unsafe;} catch (NoSuchFieldException | IllegalAccessException e) {throw new RuntimeException(e);}}
    }
    
  • ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存

    测试:

    public class d6_direct_byteBuffer {static int _1Gb = 1024*1024*1024;public static void main(String[] args) throws IOException {ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);System.out.println("分配完毕");System.in.read();System.out.println("开始释放");byteBuffer = null;System.gc();System.in.read();}
    }
    

    原理:

    // 分配内存后:
    public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);
    }DirectByteBuffer(int cap) { super(-1, 0, cap, cap, null);boolean pa = VM.isDirectMemoryPageAligned();int ps = Bits.pageSize();long size = Math.max(1L, (long)cap + (pa ? ps : 0));Bits.reserveMemory(size, cap);long base = 0;try {base = UNSAFE.allocateMemory(size);} catch (OutOfMemoryError x) {Bits.unreserveMemory(size, cap);throw x;}UNSAFE.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}try { // Cleaner检测this对象(ByteBuffer)是否被垃圾回收,如果被回收了就会调用clean方法调用 Deallocator 中的 run 方法 释放内存cleaner = Cleaner.create(this, new Deallocator(base, size, cap));} catch (Throwable t) {// Prevent leak if the Deallocator or Cleaner fail for any reasonUNSAFE.freeMemory(base);Bits.unreserveMemory(size, cap);throw t;}att = null;
    }
    // 实现 Runnable 接口
    private static class Deallocator implements Runnable {private long address;private long size;private int capacity;private Deallocator(long address, long size, int capacity) {assert (address != 0);this.address = address;this.size = size;this.capacity = capacity;}// Deallocator 中的 run 释放了内存public void run() {if (address == 0) {// Paranoiareturn;}UNSAFE.freeMemory(address); // 释放内存address = 0;Bits.unreserveMemory(size, capacity);}}// clean 方法调用 run 方法:
    public void clean() {if (!remove(this))return;try {thunk.run(); // Deallocator 中的 run 被调用} catch (final Throwable x) {AccessController.doPrivileged(new PrivilegedAction<>() {public Void run() {if (System.err != null)new Error("Cleaner terminated abnormally", x).printStackTrace();System.exit(1);return null;}});}
    }
    

1.6.3 禁用显示垃圾回收

-XX:+DisableExplicitGC 禁用显式的垃圾回收

显示垃圾回收写法:

System.gc();

这种垃圾回收都是 Full GC的,回收比较满,常用于性能调优

这可能会对Direct memory 的内存回收有影响,因为这里的Direct memory的内存回收是需要Java中的对象被回收时才会被触发的;

例如这里是ByteBuffer对象被回收,触发了分配的直接内存的回收。

所以还是用Unsafe对象手动的调用 freeMemory 进行回收比较好

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/239377.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于车轮安装MEMS IMU的航迹推算算法研究

本文由来&#xff1a;前一篇文章“零速更新(ZUPT)辅助INS定位”&#xff0c;并通过开源方案ZUPT-aided-INS进行了算法验证。在验证过程中&#xff0c;意识到在进行多源传感器融合算法中&#xff0c;利用载体自身运动信息进行约束修正非常重要&#xff0c;因为这不需要额外增加传…

前端案例—antdDesign的Select多选框组件加上全选功能

前端案例—antdDesign的Select多选框组件加上全选功能。 实现效果如下&#xff1a; Select 组件里有这个属性&#xff0c;可以利用这个对下拉菜单进行自定义。 const handleChange (e, value) > {setSelectState(e.target.checked)let arr productOptions?productOption…

【开源工程及源码】超级经典开源项目实景三维数字孪生智慧港口

智慧港口可视化平台&#xff0c;旨在实现对港口运营的全面监测、智能管理和优化决策。飞渡科技利用数字化、模拟和仿真的技术&#xff0c;通过互联的传感器和设备&#xff0c;实现实时数据的采集、传输和分析&#xff0c;将港口内外的复杂数据以直观、易懂的方式呈现&#xff0…

Qt配置opencv,cmake编译参考笔记,已成功

Qt版本&#xff1a;Qt5.14.2 opencv&#xff1a;4.5.4&#xff08;不要用4.5.5&#xff01;&#xff01;很坑别问我为什么知道&#xff09; cmake&#xff1a;下的最新版本 前言&#xff1a;为什么非得要用cmake编译呢&#xff1f;跳过cmake不好吗&#xff1f; 之前用的opencv…

服务熔断(Hystrix)

服务雪崩 多个微服务之间调用的时候&#xff0c;假设微服务A调用微服务B和微服务C&#xff0c;微服务B和微服务C又调用其他的微服务&#xff0c;这就是所谓的“扇出”&#xff0c;如果扇出的链路上某个微服务的调用响应时间过长&#xff0c;或者不可用&#xff0c;对微服务A的…

c++打开网页

1.使用ShellExecute 效果图&#xff1a; 相关代码: void Open_url::on_pushButton_clicked() {QString path1 "explorer.exe";QString urlui->lineEdit->text();ShellExecute(NULL, L"open", path1.toStdWString().c_str(), url.toStdWString().c…

Python匹配文件模块的实战技巧

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;文件匹配是许多应用中常见的需求&#xff0c;例如文件管理、数据处理等。本文将深入探讨Python中用于文件匹配的模块&#xff0c;包括glob、fnmatch和os.path等&#xff0c;通过丰富的示例…

Keil5软件仿真 定时器互补通道 波形输出(Logic Analyzer)

步骤一&#xff1a;管脚配置确认。 ①配置定时器的管脚模式为复用推挽输出模式&#xff08;GPIO_MODE_AF_PP&#xff09;&#xff01;&#xff01;&#xff01;&#xff0c;注意&#xff1a;复用开漏模式软件仿真时无波形。 步骤二&#xff1a;编译程序。 ①点击编译按钮。 …

(Mac上)使用Python进行matplotlib 画图时,中文显示不出来

【问题描述】 ①报错确缺失字体&#xff1a; ②使用matplotlib画图&#xff0c;中文字体显示不出来 【问题思考】 在网上搜了好多&#xff0c;关于使用python进行matplotlib画图字体显示不出来的&#xff0c;但是我试用了下&#xff0c;对我来说都没有。有些仅使用于windows系…

小型洗衣机什么牌子好又便宜?内衣裤洗衣机十大排名推荐

作为一个上班族&#xff0c;每天回到家中真的不愿意再动了&#xff0c;市面上也越来越多懒人福利神器&#xff0c;而内衣洗衣机可以称得上是人类最幸福的小家电&#xff0c;它不仅可以释放我们的双手&#xff0c;而且还比我们自己手洗得干净&#xff0c;功能和清洁力都比我们传…

Ubuntu 常用命令之 zip 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 Ubuntu系统下的zip命令是用来压缩文件的。这个命令可以将一个或多个文件或者目录压缩成一个.zip文件&#xff0c;也可以将整个目录树压缩成一个.zip文件。 zip命令的基本格式 zip [选项] [压缩文件名] [要压缩的文件或目录...]z…

选择移动订货系统源码的四大原因

移动订货系统需要选择源码支持的厂家&#xff0c;有以下四个原因&#xff0c;其中第四个是比较重要的&#xff0c;大家点个关注点个赞&#xff0c;我们接着往下看。 1.可自行定制&#xff1a;支持源码的移动订货系统可以根据企业的具体需求进行定制开发&#xff0c;满足企业特定…

电脑完全重装教程——原版系统镜像安装

注意事项 本教程会清除所有个人文件 请谨慎操作 请谨慎操作 请谨慎操作 前言 本教程是以系统安装U盘为介质进行系统重装操作&#xff0c;照着流程操作会清除整个硬盘里的文件&#xff0c;请考虑清楚哦&#xff5e; 有些小伙伴可能随便在百度上找个WinPE作为启动盘就直接…

运维实施工程师计算机基础

目录 一.运维实施工程师需要具备的知识 1.1.运维工程师、实施工程师是啥&#xff1f; 1.2. 运维工程师、实施工程师做些啥&#xff1f; 1.3.运维工程师、实施工程师需要具备啥技能&#xff1f; 二.计算机的组成 2.1.简介 2.1.1.CPU&#xff08;中央处理器&#xff09; 2.…

25.BFD双向转发检查

BFD双向转发检查 链路故障检测工具&#xff0c;结合三层协议使故障检测更加快速 例如两台路由器之间加了一台二层设备 在修改优先级后&#xff0c;默认选择了下面那条优先级高的路由&#xff0c;R1 ping R2的时候是正常能ping通的 但是&#xff0c;当下面的路由出现故障后&a…

Mybatis3系列课程8-带参数查询

简介 上节课内容中讲解了查询全部, 不需要带条件查, 这节我们讲讲 带条件查询 目标 1. 带一个条件查询-基本数据类型 2.带两个条件查询-连个基本数据类型 3.带一个对象类型查询 为了实现目标, 我们要实现 按照主键 查询某个学生信息, 按照姓名和年级编号查询学生信息 按照学生…

解决GD32VF103编译printf无法打印 float

解决GD32VF103编译printf无法打印 float 在GD32VF103编译后&#xff0c;采用printf打印float变量时&#xff0c;总是无法显示&#xff0c;是因为编译时采用用newlib-nano库&#xff0c;但是这个库对printf做了优化&#xff0c;在eclipse的build配置use-newlib-nano的选项取消勾…

【Amazon 实验②】使用Amazon WAF做基础 Web Service 防护之自定义规则

文章目录 1. 自定义规则1.1 介绍 2. 实验步骤2.1 测试2.2 输出 上一篇章介绍了使用Amazon WAF做基础 Web Service 防护中的Web ACLs 配置 & AWS 托管规则的介绍和演示操作 【Amazon 实验①】使用Amazon WAF做基础 Web Service 防护&#xff0c;本篇章将继续介绍关于自定义…

Web 开发与移动应用程序开发 – 哪一种适合您的业务?

目录 Web 开发&#xff1a;释放浏览器的力量 1. 可访问性和跨平台兼容性&#xff1a; 2. 成本效益&#xff1a; 3. 内容交付和搜索引擎优化&#xff1a; 4.即时更新&#xff1a; 持续的网络维护&#xff1a; 移动应用程序开发&#xff1a;针对设备功能定制体验 1.增强用户体验&…

springboot+vue前后端分离的社区养老服务管理管理系统(有文档)

springbootvue前后端分离的社区养老服务管理管理系统。系统功能齐全&#xff0c;配置完成可运行&#xff0c;有文档&#xff0c;演示视频&#xff0c;配置说明&#xff0c;数据库文件&#xff0c;虚拟产品下单不退不换&#xff01; 技术&#xff1a;springbootmybatisplusmysql…