黑马 JVM ---1 --- 内存结构

黑马 JVM

内存泄露 : 被占用的内存无法被释放

在这里插入图片描述
在这里插入图片描述

  1. 内存结构

1.1 程序计数器
1.2 虚拟机栈
1.3 本地方法栈
1.4 堆
1.5 方法区


在这里插入图片描述

1.1 程序计数器 (寄存器) (线程私有)

java源代码 转化成 jvm指令(二进制字节码) 交由 解释器 转成 机器码
再交由 CPU 进行处理

解释器 取出 程序计数器 记录的 执行地址 进行 下一条指令的执行

程序计数器 在java中 是 寄存器

程序计数器
(作用): 会 记住 下一条 JVM 指令的 执行 地址

(特点):

  1. 线程私有的 (每个线程都要一个私有的程序计数器)
  2. 不会存在 内存溢出 的 问题
  3. 随着 线程的创建 而 创建 ,线程的 销毁 而 销毁
  4. 是一块 较小的 内存空间

在这里插入图片描述

1.2 虚拟机栈 (线程私有)

(每个线程运行的时候 需要 给 每个线程 划分 一个 虚拟空间)

1.2.1
定义:

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

栈 — 线程 运行 需要的 内存空间
栈帧 : 一次栈帧 对应 一个方法的调用 (每个方法运行时 需要的 内存) (存储 参数 局部变量 返回地址)
活动栈帧 : 正在执行的 方法

调用方法 就会 给 方法 划分一个空间 并将 栈帧 压入 栈中
当方法 执行完成 就会 释放 这个 方法 所占用的 内存

一个栈 中 由 存在 多个 栈帧 组成
在这里插入图片描述

在这里插入图片描述
1.2.2
** 问题辨析 **
1.垃圾回收是否涉及 栈内存?
不需要
因为 栈内存无非就是 一次次的 方法调用 所产生的 栈帧 内存
而栈帧内存 在 每一次 方法调用 后 都会被 弹出 栈 会被自动回收掉
不需要 垃圾回收 进行 栈内存的 管理
(垃圾回收 是对 堆内存中 的 无用对象)

2.栈内存 分配 越大 越好吗?
栈内存 划分 越大 会 导致 可分配的 线程数 变少
因为 物理地址 的 大小 是固定的

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

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

安全不一定吧 因为 局部变量是线程私有的,而且 栈 是线程 私有的
主要看 是否 方法私有变量

共享的数据 才会 出现 线程 安全问题
如 static 修饰的 变量 因为这个变量 是 线程共享的

在这里插入图片描述

1.2.3
栈内存溢出

  • 栈帧过多导致栈内存溢出
  • 栈帧过大导致栈内存溢出

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述 栈溢出异常

在这里插入图片描述

top 可以监控 后台进程对 cpu的 占用情况
ps 可以

ps H -eo | grep 进程号

ps -ef | grep redis

1.2.4
线程运行诊断

案例1: cpu 占用过多
定位

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

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


在这里插入图片描述

1.3 本地方法栈 (线程私有)

定义: java 虚拟机 在调用 本地方法时 需要给这些本地方法 提供 的 一个 内存空间

本地方法(概念):不是由 java 代码 编写的 代码
因为 java有一些 不能 与 操作系统 底层 进行 操作 ,需要 c语言等 更加底层的 语言 调用 API 与操作系统进行 交流 , java代码 可以 间接 的 通过 本地方法 进行 调用 本地的一些功能

本地方法 使用 所需要的 内存 就是 本地方法栈
如 基础内库 object

public native object clone() throws CloneNotSupportedException native修饰的方法
通过本地方法接口 间接调用 实现 C语言或者c++语言的 实现
在这里插入图片描述


在这里插入图片描述

1.4 堆 Heap (线程共有)
1.4.1
定义:

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

特点

  • 线程共享, 堆中 对象 都需要 考虑 线程 安全问题
  • 垃圾回收机制 (堆中 不在 被 引用的 对象 就会被 当成 垃圾 进行 回收 以 释放 空闲的内存)

1.4.2
堆内存溢出

对象被垃圾回收的前提条件下 是该对象已经无被使用的情况下。

(设置堆内存 -Xmx8m)
在这里插入图片描述

1.4.3
堆内存诊断

1.jps工具

  • 查看当前系统中有 哪些 java进程 (jps)

2.jmap工具

  • 查看堆内存占用情况 (jmap -heap 进程号)

3.jconsoke工具

  • 图形界面的, 多功能的 监测工具 可以连续监测 (jconsoke)
  • 可以观察 堆内存使用量 线程 类 cpu占用率

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

案例: 垃圾回收后 , 内存占用 仍然 很高

工具-- jvisualvm 可视化 jvm 工具
在这里插入图片描述


在这里插入图片描述
1.5 方法区

1.5.1 定义
Java 虚拟机器具有在所有 Java 虚拟机器线程之间共享的方法区域。
方法区域类似于传统语言编译代码的存储区域,或类似于操作系统过程中的"文本"段。
它存储每类结构,如运行时间常数池、字段和方法数据,以及方法和构造器的代码,包括类和实例初始化和接口初始化中使用的特殊方法。

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

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

下列特殊情况与方法区域相关联:

如果无法提供方法区域中的内存以满足分配请求,Java 虚拟机会抛出一个 。OutOfMemoryError

1.5.2 组成
在这里插入图片描述

1.5.3 方法区内存溢出

  • 1.8 以前 会 导致 永生代 内存溢出
  • 演示 永久代 内存溢出 java.lang.OutOfMemoryError: PermGen space
  • -XX:MaxPermSize=8m
  • 1.8 之后 会 导致 元空间 内存溢出
  • 演示 元空间 内存溢出 java.lang.OutOfMemoryError: Metaspace
  • -XX:MaxMetaspaceSize=8m

场景 :
spring
mybatis
动态代理 cglib
在程序运行期间,利用 classwrite 在 内存中 动态生成 类信息
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

1.5.4 运行时常量池
-定义

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

1.5.5 StringTable

底层 : 哈希表

面试题

String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4);  false
System.out.println(s3 == s5);  true
System.out.println(s3 == s6); true
String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2); false

常量池与串池的关系
在这里插入图片描述

字符串变量拼接
在这里插入图片描述
在这里插入图片描述
编译期优化
在这里插入图片描述

字符串延迟加载
[字符串 字面量 也是 延迟 成为对象的]
在这里插入图片描述


1.5.5 StringTable 特性

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


1.5.6 StringTable 位置

在这里插入图片描述

1.8 把 StringTable 从 常量池(1.6) 中 放到 堆内存中

原因 :
永久代 的 内存效率 低 ,永久代 需要 FULL GC 的时候 才 会 触发 永久代的 垃圾回收
FULL GC 需要 整个 老年代的 内存不足 才会 触发, 触发的时机 晚, 间接到 StringTable 的回收效率不高,而 StringTable 使用次数 较多, 里面存储的 String常量, java程序中 大量的 使用 String常量对象,都会分配 到 StringTable 里面, 如果StringTable 回收效率不高, 会导致 占用 大量内存,容易产生 永久代内存不足。 而在 堆中 使用的是 Mirror GC , 垃圾回收效率高, 字符串常量 也会被 回收。

在这里插入图片描述

在这里插入图片描述

花了 96%的时间 进行 垃圾回收, 只释放了 2% 的 堆内存 而出现 oom - - UseGCOverheadLimit
(OOM)

在这里插入图片描述

1.5.7 StringTable 垃圾回收

在这里插入图片描述

1.5.8 StringTable 性能调优

  • 调整 -XX StringTableSize =桶个数
  • 考虑 将 字符串对象 是否 入池 (inter进行调优 字符串入值 减少内存)

如果 哈希表的 桶个数 比较多 那么元素 就会 相对 分散 ,哈希碰撞的 几率 就会 比较 少,查找速度也会快,
如果 桶的 个数 较少, 哈希碰撞 概率 会比较大, 链表长度也会相对较长,从而 查找的 速度 收到影响

在这里插入图片描述


1.6 直接内存
1.6.1 定义
Direct Memory

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

1.6.2 基本使用

在这里插入图片描述
切换到内核态 由cpu 函数 去 读取 磁盘文件的内容, 读取进来以后,会在 内核态的时候 在 操作系统内存中 划分一块 系统缓冲区, 磁盘内容 分次读入到 系统缓冲区
java 会在 堆内存中 分配一块java 缓冲区, 在 系统缓冲区 间接 读取到 java缓冲区
因为有两块缓冲区,会造成 数据的 不必要复制
效率低

在这里插入图片描述
调用 allocateDirect 分配一块 直接内存
会在 操作系统 划分 一块 缓冲区
java代码可以直接访问
操作系统也可以访问
两者之间 共享


1.6.3 内存溢出

在这里插入图片描述
在这里插入图片描述


1.6.4 直接内存 分配和回收原理

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

//需要我们 主动 调用 freeMemory ()释放内存

//cleaner 对象 : 虚引用类型
//关联的对象被 引用 时候, 当对象被垃圾回收的时候, 就会触发 虚引用的 clean 方法


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值