2.JVM自动内存管理

2023-10-29

2.自动内存管理

2.1 概述

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。

C++需要对构建的每个对象的生命周期进行管理和维护。

Java把对象的生命周期(内存管理)交给了JVM,简化编码,但一旦出现问题则很难排查。

2.2 运行时数据区

在这里插入图片描述

Java运行时将内存分为不同区域以方便进行内存管理(例如垃圾回收)。

分为线程私有和线程共享

线程私有

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

线程共享

  1. 方法区

2.2.1 程序计数器

程序计数器(Program Counter Register)是唯一一块不会产生OOM的区域

功能:

  1. 在线程切换时保存现场。在现场恢复运行时恢复现场
  2. 实现语句的判断分支功能

2.2.2 Java虚拟机栈

生命周期与线程相同,描述Java方法执行的线程内存模型

一个方法执行从开始到结束与一个虚拟机栈的**栈帧(Stack Frame)**从入栈到出栈相同

栈帧中存储了

  1. 局部变量表
  2. 操作数栈
  3. 动态连接
  4. 方法出口

JVM可被笼统的划分为堆和栈这里的占据是只Java虚拟机栈,或者说指虚拟机栈中的局部变量表部分

局部变量表存放了两种数据

  1. 基本数据类型(primitive)

    直接指向基本数据类型的值

  2. 对象引用

    对象引用两种实现

    • 直接指向对象在内存中的起始地址,这就要求对象在对象头中记录对象所述的class信息
    • 句柄池,由句柄池去指向对象及对象的class信息,可进行解耦减少垃圾回收导致的对象移动对引用产生影响

产生OOM

  1. 方法不断循环调用自身并且非尾递归造成栈帧越界

    stackOverFlow

  2. 若栈支持可扩展,并且无法申请到足够的内存,产生OutOfMemoryError

2.2.3 本地方法栈

调用本地方法服务

《Java虚拟机规范》没有对本地方法栈中方法使用的语言、方式做强制规定,在Hotspot中将本地方法栈与虚拟机栈合二为一。

2.2.4 Java堆

线程共享的内存区域,在虚拟机启动时创建。

几乎所有实例都在这里被分配

思考:若堆是线程共享,那么线程私有变量如何存放,threadlocal中如何实现线程私有变量

推论:在堆中开辟线程私有的空间

如果从分配内存的角度来看,所有线程共享的Java堆中可划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配效率。

在后面会提到对象初始化时的内存分配策略:指针碰撞或空闲列表

Java堆可通过-Xms-Xmx来指定大小,两者可设置成一样避免堆扩容造成的性能浪费。

堆空间不足抛出OutOfMemoryError异常。

2.2.5 方法区

方法区(Method Area)与Java堆一样是线程共享区域,用于存储已被虚拟机加载的类型信息,静态变量,常量,即时编译器编译后的代码缓存等数据。

方法区可视作存储不易变化的对象的地方,例如

  1. 常量
  2. 多次GC仍未被回收的对象
  3. class信息
  4. etc

永久代≠方法区

只不过hotspot将分代设计扩展至方法区或者说用永久代实现方法区,方便与堆一样管理方法区内存

方法区空间不足OOM

jdk8后完全废弃永久代概念,用元空间(Meta Space)代替

2.2.6 运行时常量池

方法区的一部分

用于存放class文件除了基础信息还有常量池表(Constant Pool Table)

常量池空间不足OOM

2.2.7 直接内存

NIO、unsafe都可直接分配内存导致OOM

2.3 对象探秘

2.3.1对象的创建

  1. 在常量池找到合适的class信息
  2. 判断能否被实例化
  3. 根据内存情况(内存情况由垃圾收集器对应的收集算法导致)决定如何分配内存
    • 指针碰撞,使用带整理的算法(标记-复制、标记-整理)通过中间指针将内存划分为已分配和未分配,对象内存分配简单,只需移动指针
    • 空闲列表,使用不带整理的算法(标记-清除),由于算法产生内存碎片所以不适合指针碰撞,需要维护一块空闲列表让对象的内存分配在逻辑上的连续的
  4. 分配的内存空间初始化
  5. 设置对象头(hashcode,分代年龄,锁,class等信息)
  6. 执行对象初始化方法
  7. 返回对象指针

指针碰撞带来的问题:多线程情况下分配内存产生并发问题

解决方案

  1. CAS 异步转同步

    Atomic::cmpxchg

  2. 每个线程分配一块自己的内存空间(Thread Local Allocation Buffer),只有用完了才需要采用同步机制扩容

通过命令-XX: +/-UseTLAB控制

2.3.2 对象的内存布局

三部分组成

  1. 对象头(Header)
    • 对象自身运行数据(Mark Word)
      • HashCode
      • GC分代信息
      • 锁状态标志位
      • 线程持有的锁
      • 偏向线程ID
      • 偏向时间戳
    • class信息
  2. 实例数据(Instance Data)
  3. 对其填充(Padding)

2.3.3 对象的访问定位

reference

  1. 句柄池

    解耦,避免GC堆引用产生影响

  2. 直接内存

    简单,速度快,hotSpot主要采用

在这里插入图片描述

2.4 OutOfMemory异常

2.4.1 Java堆溢出

不断创建对象,并且保持对象被可达性

在这里插入图片描述

要解决这个内存区域的异常,常规的处理方法是首先通过内存映像分析工具(如Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析。第一步首先应确认内存中导致OOM的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。图2-5显示了使用Eclipse Memory Analyzer打开的堆转储快照文件。

2.4.2虚拟机栈和本地方法栈溢出

栈容量-Xss设定

  1. 栈超出深度 stackOverFlow

    方法的递归调用(非尾递归) 造成广度溢出

    某个方法调用的primitive参数太多造成宽度溢出

  2. 栈允许扩容(HotSpot不支持扩容)但无法申请足够内存OutOfMemory

2.4.3方法区和运行时常量池溢出

运行时常量池是方法区的一部分

方法区=运行时常量池+永久代

通过String.value(i++).intern()并限制永久代大小导致OOM

字符串常量池在JDK7之后放到堆中,用-XX:PermSize=6M -XX:MaxPermSize=6M限制永久代大小用上面方法无效,需限制堆大小

String对象调用intern()方法会将对象指向某个常量池引用

@Test
public void test8(){
    // jdk8
    // true
    String str1 = new StringBuilder("计算机").append("软件").toString();
    System.out.println(str1.intern() == str1);

    // false
    String str2 = new StringBuilder("ja").append("va").toString();
    System.out.println(str2.intern() == str2);
}

jdk6 双false

jdk7 true false

原因:jdk6 intern首次遇到字符放入常量池并返回常量池引用,而StringBuilder#toString是new String代表是在堆上分配空间,因此两者不同是false

jdk7之后字符串不需要拷贝到永久代,常量池在堆中因此是true

java这个字符串特殊

这是因为“java”[插图]这个字符串在执行String-Builder.toString()之前就已经出现过了,字符串常量池中已经有它的引用,不符合intern()方法要求“首次遇到”的原则,“计算机软件”这个字符串则是首次出现的,因此结果返回true。

方法区溢出可通过反射循环构建方法实现

jdk8后前面的举例很难产生异常因为把方法区放到了元空间中

可通过设置参数来控制元空间大小

·-XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。·-XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值。·-XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio,用于控制最大的元空间剩余容量的百分比。

2.4.4 本机直接内存溢出

直接内存(Direct Memory)可通过-XX:MaxDirectMemorySize指定否则与-Xmx一样

可通过unsafe直接分配内存

小结

  1. 讲述内存区域划分
  2. 对象创建过程
  3. OOM

OOM各情况

  1. 栈溢出 stackOverFlow OOM
  2. 堆溢出
  3. 方法区溢出,运行时常量池溢出
  4. 线程过多
  5. 直接内存溢出
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

2.JVM自动内存管理 的相关文章

  • Hashmap并发问题

    我有一个哈希图 出于速度原因 我希望不需要锁定 假设我不介意过时的数据 同时更新它和访问它会导致任何问题吗 我的访问是获取 而不是迭代 删除是更新的一部分 是的 这会导致重大问题 一个例子是向散列映射添加值时可能发生的情况 这可能会导致表重
  • jvm 次要版本与编译器次要版本

    当运行使用具有相同主要版本但次要版本高于 JVM 的 JDK 编译的类时 JVM 会抛出异常吗 JDK 版本并不重要 类文件格式版本 http blogs oracle com darcy entry source target class
  • 使用 Java 在 WebDriver 中按 Ctrl+F5 刷新浏览器

    我已经使用 java 刷新了 WebDriver 中的浏览器 代码如下 driver navigate refresh 如何使用 Java 在 WebDriver 中按 Ctrl F5 来做到这一点 我认为您可以使用 WebDriver 和
  • 在哪里可以获得有关 Java FitNesse 和 Slim 的一些教程? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何开始使用 Chainsaw for Log4j?

    我想开始使用 Chainsaw v2 几乎没有关于它的信息 我只找到了this http www velocityreviews com forums t140105 help using chainsaw for log4j html 但
  • 如何比较 Struts 2 中 url 请求参数中的单个字符

    我正在读取具有单个字符的 url 参数 它将是Y or N 我必须写一个条件来检查它是否Y or N并做相应的事情 这是我写的 但似乎不起作用 总是转到其他地方 网址是
  • java.lang.LinkageError:尝试重复的类定义

    为什么会发生错误以及如何修复它 02 13 02 pool 4 thread 2 WARN Exception in thread pool 4 thread 2 02 13 02 pool 4 thread 2 WARN java lan
  • 字符串池可以包含两个具有相同值的字符串吗? [复制]

    这个问题在这里已经有答案了 字符串池可以包含两个具有相同值的字符串吗 String str abc String str1 new String abc Will the second statement with new operator
  • Java Microsoft Excel API [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 容器中的 JVM 计算处理器错误?

    最近我又做了一些研究 偶然发现了这一点 在向 OpenJDK 团队抱怨之前 我想看看是否有其他人观察到这一点 或者不同意我的结论 因此 众所周知 JVM 长期以来忽略了应用于 cgroup 的内存限制 众所周知 现在从 Java 8 更新某
  • 所有平台上的java

    如果您想用 java 为 Windows Mac 和 Linux 编写桌面应用程序 那么所有这些代码都相同吗 您只需更改 GUI 即可使 Windows 应用程序更像 Windows 等等 如果不深入细节 它是如何工作的 Java 的卖点之
  • Jenkins 的代码覆盖率 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何找到被点击的JLabel并从中显示ImageIcon?

    这是我的代码 我想知道哪个l单击 然后在新框架中显示该 ImageIcon e getSource 不起作用 final JFrame shirts new JFrame T shirts JPanel panel new JPanel n
  • 如何将 arraylist 从 servlet 传递到 javascript?

    我通过在属性中设置数组列表并将其转发到 jsp 来从 servlet 传递数组列表 Servlet ArrayList
  • JSch中如何设置文件类型和文件传输模式?

    我使用 Apache Common NetFTPClient并设置了我的ftpClient在上传文件之前使用如下所示的方法 ftpClient setFileType FTP BINARY FILE TYPE ftpClient setFi
  • 改变for循环的顺序?

    我遇到一种情况 我需要根据用户输入以不同的顺序循环遍历 xyz 坐标 所以我是 3D 空间中的一个区域 然后是一组像这样的 for 循环 for int x 0 x lt build getWidth x for int y 0 y lt
  • Java中的回调接口是什么?

    SetObserver 接口的代码片段取自有效的Java 避免过度同步第67条 public interface SetObserver
  • 如何用表达式语言获取布尔属性?

    如果我有一堂这样的课 class Person private int age public int getAge return age public boolean isAdult return age gt 19 我可以得到age像这样
  • 春季 CORS。在允许的来源中添加模式

    查看CORS的弹簧指南 以下代码启用所有允许的来源 public class MyWebMVCConfigurer extends WebMvcConfigurerAdapter Override public void addCorsMa
  • 为什么应该首选 Java 类的接口?

    PMD https pmd github io 将举报以下违规行为 ArrayList list new ArrayList 违规行为是 避免使用 ArrayList 等实现类型 而是使用接口 以下行将纠正违规行为 List list ne

随机推荐