2.JVM自动内存管理

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. 直接内存溢出
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值