JVM 内存结构

news2024/11/26 14:39:27

文章目录

  • 1、程序计数器
  • 2、虚拟机栈
    • 2.1 、定义
    • 2.2、栈内存溢出
    • 2.3 、线程运行诊断
  • 3、本地方法栈
  • 4、堆
    • 4.1、定义
    • 4.2 、堆内存溢出
    • 4.3 、堆内存诊断
  • 5、方法区(Method Area)
    • 5.1 、定义
    • 5.2、方法区组成
    • 5.3 、方法区内存溢出
    • 5.4 、运行时常量池
    • 5.5 、StringTable
    • 5.6 、StringTable位置
    • 5.7 、StringTable垃圾回收
    • 5.8 、StringTable性能调优
  • 6、直接内存
    • 6.1 、定义
    • 6.2 、文件读取过程
    • 6.3 、直接内存释放原理

先来一张 JVM 架构图

image-20220531144228815

jvm内存结构:

  1. 程序计数器
  2. 虚拟机栈
  3. 本地方法栈
  4. 方法区

在这里插入图片描述

  • ClassLoader类加载器:Java 代码编译成二进制后,会经过类加载器,这样才能加载到 JVM 中运行。
  • Method Area方法区:类是放在方法区中。
  • Heap堆:类的实例对象。
  • 当类调用方法时,会用到 JVM Stack本地方法栈PC Register
  • 方法执行时的每行代码是由执行引擎中的解释器逐行执行,方法中的热点代码频繁调用的方法,由 JIT 编译器优化后执行,GC 会对堆中不用的对象进行回收。需要和操作系统打交道就需要使用到本地方法接口

1、程序计数器

在这里插入图片描述

在java中使用CPU寄存器作为程序计数器

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

特点:

  1. 线程私有的,每个线程都有自己的程序计数器,用来记录程序运行到了那个位置
  2. 不会存在内存溢出(java中唯一不用考虑内存溢出的地方)

2、虚拟机栈

在这里插入图片描述

2.1 、定义

Java Virtual Machine Stacks(Java虚拟机栈)

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

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

不涉及,因为栈内存会随着方法出栈释放掉,不需要GC管理。GC只回收堆内存的无用垃圾

栈内存的分配越大越好吗?

不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。

可以通过JVM指令来分配栈空间,如果不指定会默认分配 Java官方文档

image-20220531151856031

方法内的局部变量是否线程安全?

  • 如果方法内部局部变量没有逃离方法的作用访问,它是线程安全的
  • 如果是局部变量引用了对象,并逃离了方法,就不是线程安全的

每一个线程都会产生一个单独的栈帧(局部变量是线程私有的),不会出现共享资源抢占的问题,所以不会有线程安全问题

但是如果是变量是static,是多个线程共享的会产生线程安全问题

2.2、栈内存溢出

以下情况可能会导致栈内存溢出:

  • 栈帧过多导致栈内存溢出(例如不合理的递归调用)
  • 栈帧过大导致栈帧溢出
  • 类的循环引用导致内存溢出

栈帧过大、过多、或者第三方类库操作,都有可能造成栈内存溢出 java.lang.stackOverflowError

使用 -Xss256k 指定栈内存大小

2.3 、线程运行诊断

案例1: cpu 占用过多

解决方法:Linux 环境下运行某些程序的时候,可能导致 CPU 的占用过高,这时需要定位占用 CPU 过高的线程

  • top命令定位哪个进程对cpu的占用过高
  • ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
  • jstack 进程id
    • 可以根据线程id (十进制转换成十六进制)找到有问题的线程,进一步定位到问题代码的源码行号

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

有可能是死锁

  • jstack 进程id 定位问题代码行号

3、本地方法栈

image-20220531174919845

本地方法栈就是存放native方法的空间,线程私有

4、堆

image-20220531180653959

4.1、定义

Heap 堆

  • 通过new关键字,创建对象都会使用堆空间

特点:

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

4.2 、堆内存溢出

java.lang.OutOfMemoryError: Java heap space堆内存溢出

使用-Xmx8m来指定堆内存大小

4.3 、堆内存诊断

  1. jps工具

  2. jmap工具

    • 查看堆内存占用情况:jmap -heap 进程id
  3. jconsole 工具

    • 图形界面的,多功能的监测工具,可以连续监测
  4. 垃圾回收后,内存占用仍然很高,可以使用 jvisualvm工具 堆转储(dump)

5、方法区(Method Area)

image-20220531185124696

5.1 、定义

方法区官方定义:

The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the “text” segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.

Java 虚拟机有一个方法区域,该区域在所有 Java 虚拟机线程之间共享。方法区域类似于常规语言或操作系统进程中的“文本”段的编译代码的存储区域。它存储每个类的结构,如运行时常量池字段方法数据,以及方法和构造函数的代码,包括用于类和实例初始化和接口初始化的特殊方法(2.9)

The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.

方法区域在虚拟机启动时创建。虽然方法区域在逻辑上是堆的一部分,但简单实现可以选择不对其进行垃圾收集或压缩。本规范并不要求方法区域的位置或用于管理已编译代码的策略。所述方法区域可以是固定的大小,或者可以根据计算的要求扩大,如果不需要更大的方法区域,则可以缩小。方法区域的内存不需要是连续的

A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.

Java 虚拟机实现可以为程序员或用户提供对方法区域的初始大小的控制,以及在变大小方法区域的情况下对最大和最小方法区域大小的控制。

The following exceptional condition is associated with the method area:

下列异常情况与方法区域相关联:

  • If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.

    如果方法区域中的内存不能用于满足分配请求,则 Java 虚拟机抛出 OutOfMemoryError。

5.2、方法区组成

Hotspot JVM的结构,可以看到堆空间发生了改变

image-20220531190648933

5.3 、方法区内存溢出

1.8 以前会导致永久代(PermGen)内存溢出

演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space
 -XX:MaxPermSize=8m

1.8 之后会导致元空间内存溢出

 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
 -XX:MaxMetaspaceSize=8m

5.4 、运行时常量池

二进制字节码注册(类基本信息、常量池、类方法定义、虚拟机指令)

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

5.5 、StringTable

StringTable的特性:

  • 常量池中的字符串仅是符号,第一次用到时才会转化为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理StringBuilder(1.8)
  • 字符串常量拼接的原理编译器优化
  • 可以使用 intern 方法,主动将串池中换没有的字符串对象放入串池
    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会将串池中的对象返回
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,再放入串池(创建了两个对象),会把串池中的对象返回

5.6 、StringTable位置

不同版本的JVM的StringTable存放的位置不一样,jdk1.6以后的版本将StringTable的位置由PermGen改到了Heap中,主要是因为永久代的垃圾回收需要Full GC(重量级GC),而Heap只需要Monir GC就能回收垃圾,而常量池是经常用到所以进行了优化

image-20220601094510963

5.7 、StringTable垃圾回收

我们先演示一段简单的代码

public class GCStringTable {
    public static void main(String[] args) {
        int i = 0;
        try {

        }catch (Throwable e){
            e.printStackTrace();
        }finally {
            System.out.println(i);
        }
    }
}

需要添加一些虚拟机参数再执行:

#堆空间大小      打印信息StringTable的信息       打印GC信息(GC次数、时间等)
-Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc	

执行结果为:

0
Heap
 PSYoungGen      total 2560K, used 1892K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 92% used [0x00000000ffd00000,0x00000000ffed9180,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 7168K, used 0K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffd00000)
 Metaspace       used 3242K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 344K, capacity 388K, committed 512K, reserved 1048576K
SymbolTable statistics:
Number of buckets       :     20011 =    160088 bytes, avg   8.000
Number of entries       :     13500 =    324000 bytes, avg  24.000
Number of literals      :     13500 =    602424 bytes, avg  44.624
Total footprint         :           =   1086512 bytes
Average bucket size     :     0.675
Variance of bucket size :     0.674
Std. dev. of bucket size:     0.821
Maximum bucket size     :         6
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :      1698 =     40752 bytes, avg  24.000
Number of literals      :      1698 =    173864 bytes, avg 102.393
Total footprint         :           =    694720 bytes
Average bucket size     :     0.028
Variance of bucket size :     0.028
Std. dev. of bucket size:     0.169
Maximum bucket size     :         3

我们看第20行StringTable statistics,StringTable底层的实现是HashTable,我们知道HashTable的底层实现是数组+链表+红黑树

数组的个数我们称为bucket(桶),桶的个数我们称之为buckets,对应第27行,我们可以看到在本例中有60013个桶,第22行Number of entries表示键值对共有1698个,第23行literals表示字符串常量的个数,有1698个

我们现在更改一下代码:

public class GCStringTable {
    public static void main(String[] args) {
        int i = 0;
        try {
            for (int j = 0; j < 100; j++) {
                String.valueOf(j).intern();//将字符串入串池
                i++;
            }
        }catch (Throwable e){
            e.printStackTrace();
        }finally {
            System.out.println(i);
        }
    }
}

我们将100个字符串加入到字符串串池也就是StringTable中,再执行一下:

StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :      1795 =     43080 bytes, avg  24.000
Number of literals      :      1795 =    178496 bytes, avg  99.441
Total footprint         :           =    701680 bytes
Average bucket size     :     0.030
Variance of bucket size :     0.030
Std. dev. of bucket size:     0.173
Maximum bucket size     :         3

可以看到100个字符串并没有达到我们设置堆空间的阈值10M,现在还没有触发垃圾回收,现在我们将字符串的数量增大到10000,查看结果:

StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :      7514 =    180336 bytes, avg  24.000
Number of literals      :      7514 =    453560 bytes, avg  60.362
Total footprint         :           =   1114000 bytes
Average bucket size     :     0.125
Variance of bucket size :     0.131
Std. dev. of bucket size:     0.362
Maximum bucket size     :         3

我们发现Number of literals的值并没有增加10000,我们可以看到最上面有一行代码:

[GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->866K(9728K), 
0.0017615 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

这行代码表示堆空间不足,触发了新生代的Monir GC,速度还是很快的

5.8 、StringTable性能调优

我们知道StringTable的底层是hashTable,hashTable的性能和元素个数也就是桶的数量有关的。

如果hashTable中桶的个数比较多,那么它就越分散,hash碰撞的几率就越小

如果桶的数量较少,它就越集中,hash碰撞的几率就越大

那么如何调优呢?

  • 调整 -XX:StringTableSize=桶个数(最少设置为 1009 以上)
  • 考虑将字符串对象是否入池(用intern函数入池,如果池中有则返回池中的对象)

6、直接内存

6.1 、定义

Direct Memory (直接内存)不属于JVM管理,属于操作系统内存

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

6.2 、文件读取过程

我们在使用NIO进行数据读取时的流程为:

image-20220601111316318

可以看到其实读一个文件会产生两个缓存区,分别是系统缓存区Java缓存区,这样数据明显冗余了,所以传统NIO方式读取文件效率低下

我们来看BIO方式下读取:

image-20220601111500612

我们通过ByteBuffer.allocateDirect获取了一块直接内存(direct memory),这块直接内存是操作系统和Java都可以共同访问的,所以读写性能是非常快的

6.3 、直接内存释放原理

  • 使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用Unsafe#freeMemory方法
  • ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存

直接内存的释放是调用了unsafe类进行释放的

unsafe#allocateMemory(int size) //获得申请空间的地址

unsafe#setMemory(int size) // 申请空间

unsafe#freeMemory(long base) // 释放空间,需传入需要释放空间的地址

我们看对应源码

ByteBuffer#allocateDirect(int capacity)

image-20220601114424490

现在我们知道了当ByteBuffer对象被GC的时候,才会回收我们的直接内存,但是有的时候为了防止程序员频繁使用System.gc()(这是full GC),我们会用命令关闭手动full gc

-XX:+DisableExplicitGC   //关闭显示full GC

这个时候我们的ByteBuffer对象如果没有被回收就会导致直接内存一直不会被回收

我们的解决方法是调用

unsafe#freeMemory(long base) // 释放空间,需传入需要释放空间的地址

手动释放内存

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

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

相关文章

【JavaEE】TCP网络原理

目录 1.TCP协议定义 2.TCP原理 2.1确认应答机制 2.2超时重传机制 2.3连接管理 2.3.1建立连接&#xff08;三次握手&#xff09; 2.3.2断开连接&#xff08;四次挥手&#xff09; 2.4滑动窗口 2.5流量控制 2.6拥塞控制 2.7延迟应答 2.8捎带应答 2.9面向字节流&…

【STC8A8K64D4开发板】——按键检测

第2-3讲&#xff1a;按键检测 学习目的学习轻触按键和触摸按键硬件电路原理。学习STC8A8K64D4用作输入时相关寄存器的配置。掌握如何读取GPIO状态。掌握编写轻触按键和触摸按键检测程序。 硬件电路设计 IK-64D4开发板上设计了4个轻触按键和一个触摸按键&#xff0c;提供给用户作…

企业级信息系统开发讲课笔记2.3 利用MyBatis实现关联查询

文章目录零、本节学习目标一、查询需求&#xff08;一&#xff09;针对三张表关联查询&#xff08;二&#xff09;按班级编号查询班级信息&#xff08;三&#xff09;查询全部班级信息二、创建数据库表&#xff08;一&#xff09;创建教师表&#xff08;二&#xff09;创建班级…

BUUCTF-WEB-INF/web.xml泄露-SSTI注入

第八周 目录 WEB [RoarCTF 2019]Easy Java WEB-INF/web.xml泄露 WEB-INF/web.xml泄露原因 WEB-INF/web.xml泄露利用方法 解决方法 [BJDCTF2020]The mystery of ip 什么是板块注入 SSTI 为什么会产生 什么是render_template render_template&#xff1a; 我们为什么…

背包问题-动态规划

背包问题 容量有限的背包&#xff0c;给很多商品&#xff0c;商品有相应的体积与价值 01背包问题 一个背包 每个物品只有一个 最终状态方程 dp[i][j]max(dp[i-1][j],dp[i-1][j-w[i]]v[i]) 推导过程 由上一层推导过来 选择拿不拿i 最终代码 #include<iostream> #…

第12届蓝桥杯省赛真题剖析-2020年12月20日Scratch编程中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第124讲。 第12届蓝桥杯省赛举办了两次&#xff0c;这是2020年10月20日举行的第一次省赛中级组试题&#xff0c;比赛仍…

【Java实战篇】Day6.在线教育网课平台

文章目录一、需求&#xff1a;绑定媒资1、需求分析2、库表设计与模型类3、接口定义4、Mapper层开发5、Service层开发6、完善controller层二、需求&#xff1a;课程预览1、需求分析2、实现技术3、模板引擎4、Freemarker入门5、部署网站门户6、接口定义7、接口开发8、编写模板9、…

放弃 console.log 吧!用 Debugger 你能读懂各种源码

很多同学不知道为什么要用 debugger 来调试&#xff0c;console.log 不行么&#xff1f; 还有&#xff0c;会用 debugger 了&#xff0c;还是有很多代码看不懂&#xff0c;如何调试复杂源码呢&#xff1f; 这篇文章就来讲一下为什么要用这些调试工具&#xff1a; console.lo…

PostgreSQL技术内幕(七)索引扫描

索引概述 数据库索引&#xff0c;是将一个表的某些字段的数据进行重新组织的数据库对象。通过使用索引&#xff0c;可以大大加速数据库的一些操作&#xff0c;其背后的思想也很简单朴素&#xff1a;空间换时间。 数据库中的索引&#xff0c;可以类比为一本书的目录&#xff0…

linux java中使用POI将word转为PDF时无法显示文字

背景: 在windos上本地调试时使用POI将word转为PDF时, PDF无法显示文字的原因以及解决方案: 我的是在linux7.9上&#xff0c;原因是生成world时候汉字正常&#xff0c;转pdf时没有汉字&#xff0c;多次调查后发现没有 宋体: 原因1:字体不存在问题, word中使用的字体在系统(wind…

udp 版本的 echo server 和 echo client

文章目录前言UDP数据报套接字编程什么是套接字套接字的api示例&#xff1a;一发一收&#xff08;无响应&#xff09;客户端服务端前言 基于udp socket写一个最简单的客户端服务器程序. UDP数据报套接字编程 什么是套接字 我们先来解释一下什么是套接字吧! 套接字&#xff0…

流浪地球2:AI人工智能+数字生命+元宇宙

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D开发工具链剧情介绍 太阳危机 太阳即将老化膨胀&#xff0c;吞没太阳系&#xff0c;地球上的人类构思了各种生存计划&#xff1a;其一是“数字生命计划”&#xff0c;该计划制造强大的量子计算机&#xff0c;希望让人类在数字世界…

D. Omkar and Circle(非常有意思的一道题)

Problem - D - Codeforces 丹尼是当地的数学狂人&#xff0c;他对圆形很着迷&#xff0c;这是奥姆卡最近的发明。帮他解决这个圆的问题!已知n个非负整数a1, a2&#xff0c;&#xff0c; an&#xff0c;它们排成一个圆&#xff0c;其中n必须是奇数。n -1能被2整除)。形式上&…

基于Tensorflow搭建卷积神经网络CNN(人脸识别)保姆及级教程

项目介绍 TensorFlow2.X 搭建卷积神经网络&#xff08;CNN&#xff09;&#xff0c;实现人脸识别&#xff08;可以识别自己的人脸哦&#xff01;&#xff09;。搭建的卷积神经网络是类似VGG的结构(卷积层与池化层反复堆叠&#xff0c;然后经过全连接层&#xff0c;最后用softm…

KD-2125地下电缆测试仪

一、产品概述 管线探测仪是一套高性能地下金属管线探测系统&#xff0c;由信号发射机和接收机组成&#xff0c;可用于金属管线、地下电缆的路径探测、管线普查和深度测量&#xff0c;配合多种选配附件&#xff0c;可以进行唯一性鉴别&#xff0c;以及管道绝缘破损和部分类型电缆…

HTML—javaEE

文章目录1.认识HTML2.HTML标签的使用2.1注释2.2标题2.3段落2.4换行2.5字体加粗、斜体字、删除线、下划线2.6图片2.7超链接2.8表格2.9列表2.10表单标签2.11div2.12span3.HTML特殊符号1.认识HTML &#xff08;1&#xff09;HTML是网页的编程语言&#xff0c;文件的内容主要由“标…

【从零开始学Skynet】实战篇《球球大作战》(十一):战斗场景设计

现在的服务端框架有支撑数万玩家的能力&#xff0c;且支持横向拓展&#xff08;即 增加物理机数量&#xff09;&#xff0c;理论上具有无上限的负载能力。下面以《球球大 作战》为例&#xff0c;说明怎样使用这套框架。1、战斗流程 玩家登录后&#xff0c;玩家可以做些非战斗操…

形式语言和自动机总结DFA、NFA

第一章DFA 形式定义和状态转移函数: DFA是一种特殊的NFA&#xff0c; A{Q,,,,F} Q:输入状态集&#xff0c;∑:字母表&#xff0c;δ:状态转移函数Q∑→Q q0∈Q初始状态 F终结集 设计举例 1.设计接受偶数个0和偶数个1串的DFA 2.设计 DFA 接受 {0,1} 上的字符串 w, 且 w 是 …

C++之模拟实现map和set

文章目录前言一、迭代器1.begin()和end()2.operator()二、改造红黑树三、map的模拟实现四、set的模拟实现总结前言 基于之前的红黑树和map、set的相关知识&#xff0c;本节我们使用红黑树来模拟实现STL中的map和set。 一、迭代器 使用迭代器可以方便我们对数据结构进行遍历&a…

windows安装wsl2

总的来说是按照这三个链接来的&#xff0c;也写了一个大体流程。 wsl对win版本有要求&#xff0c;可以 winr winver查看 原始参考链接&#xff1a; 1&#xff09;https://zhuanlan.zhihu.com/p/466001838 2&#xff09;https://cloud.tencent.com/developer/article/1986728 3&…