2、JVM 内存结构

本栏目讲叙JVM简介、JVM内存结构、垃圾回收机制和类加载与字节码技术


程序计数器

1、概述

  • :用于记录下一条 JVM 指令的执行地址,是线程私有的,不存在内存溢出的情况

2、工作流程

  • 解释器程序计数器中获取下一条 JVM 指令执行地址,解释器根据地址找到相关指令,将指令转换为机器码,再交由 CPU 执行

虚拟机栈

1、概述

  • 线程运行时所需要的内存空间由多个栈帧构成(栈帧由参数、局部变量和返回地址等组成),且每个线程只能有一个活动栈桢

2、栈内存溢出

  • 栈帧过多(方法递归调用层级过多)
  • 栈帧过大

3、查看线程情况

# 方式1:使用linux相关指令查看
# 查看进程CPU、内存的占用情况,还可以知道进程的PID
top
# 根据进程PID可以查看该进程下的所有线程的占用情况
ps H -eo pid,tid,%cpu | grep <pid>

# 方式2:使用jdk相关指令查看
# 查看Java进程,获取进程pid
jps
# 根据进程PID查看该进程下所有线程,再根据TID去查看原因
jstack <pid>

注意:单个栈空间分配越多,所能创建的线程数就越少


本地方法栈

  • 概述:调用本地方法(C/C++所编写的方法)运行时所需要的内存空间

堆(Heap)

1、概述

  • 通过 new 关键字,创建的对象(包含对应的 class 信息=>方便获取对应的操作指令)都会使用堆内存。它是线程共享的,堆中的对象都需要考虑线程安全问题,具有垃圾回收机制

2、组成

  • 新生代:由伊甸园、幸存区 From 和幸存区 To 组成,伊甸园用来存放临时引用的对象,幸存区 To 存放还有引用的对象,有希望进阶到永久代
  • 永久代:存储长期使用的对象

3、查看堆情况

# 方式1:使用命令
# 查看Java进程,获取进程pid
jps 
# 根据进程PID查看堆内存占用情况
jmap -heap <pid>

# 方式2:使用图形工具
1、JConsole
2、JVisualVM

方法区

1、简介

  • 概述存储类结构相关的信息(类名称、成员变量、成员方法和构造函数等)和运行时常量池。该区域被线程共享
  • 组成
    • JDK 1.6 之前:Class、ClassLoader和运行常量池(StringTable)使用永久代实现,存放在堆中
      方法区1

    • JDK1.8 之后:Class、ClassLoader和运行常量池使用元空间实现,存放在直接内存中,而StringTable存放在堆中
      方法区2

2、常量池

  • 概述:就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
  • 存储内容
    • 符号引用
      • 类和结构的完全限定名
      • 字段名称和描述符
      • 方法名称和描述符
    • 字面量
      • 文本字符串
      • final常量值
      • 基本数据类型的值

3、运行时常理池

  • 概述运行期间产生的常量类加载后常量池中的信息都会存放到运行时常量池

4、StringTable

  • 概述:运行时常量池的一部分,用于存储字符串对象,由一个 HashTable 组成
  • 特性
// 常量池中的信息在类加载时加载到运行时常量池中,但这时像"Hello "常量在运行时常量池中只是符号,
// 只有在执行引擎加载该字符串的指令时,才会创建字符串对象,然后在StringTable中查找,
// 如果没有则存入该对象,如果有则使用StringTable中的对象 
String s1 = "Hello ";
String s2 = "World";

// 字符串变量拼接的原理是在堆中new StringBuilder()对象去拼接
String s3 = s1 + s2;   

// 字符串常量拼接原理是编译期优化,即在运行时已经优化成s4 = "Hello World"
String s4 = "Hello " + "World";

// 使用intern方法,主动将字符串对象放入StringTable
s4.intern();
  • 特性
// 设置串池的大小桶大小
-XX:StringTableSize=<size>    例:-XX:StringTableSize=200000

// 打印串池中的统计信息
-XX:+PrintStringTableStatistics


// 案例:如果需要存放大量的字符串,且字符串有大量的重复,可以先让字符串入池(调用intern方法),
//      再将返回结果放在堆中,节约堆内存的使用
public static void main(String[] args) {
    try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(new FileInputStream("words.txt"), StandardCharsets.UTF_8))) {
        String word;
        while (true) {
            word = reader.readLine();
            if (null == word) {
                break;
            }
            word.intern();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

5、直接内存

  • 概述即系统内存,常用于 NIO 操作时的数据缓冲区
  • 特点
    • 分配和回收成本高,但读写性能高
    • 不受 JVM 内存回收管理
  • 分配与回收
    • 创建 ByteBuffer 对象时使用 Unsafe 对象完成直接内存的分配
    • ByteBuffer 内部使用了 Cleaner 对象(虚引用)来监测该对象,当该对象被回收时,那么就会由 ReferenceHandler 线程通过 Cleaner 对象的 clean() 方法调用 Unsafe的freeMemory() 来释放直接内存
  • 禁用显示回收:-XX:DisableExplictGC
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值