JVM:Java运行时数据区内存模型与内存异常

什么是内存管理
对内存空间进行分配和回收,即内存动态分配和垃圾回收
Java:虚拟机自动管理
C/C++:程序员手动管理
!!自动管理相比于手动管理不容易出现内存泄露和内存溢出,但仍然存在内存泄漏和溢出的问题

虚拟机规范足够严谨也足够宽松。严谨是为了正确性,宽松是为了实现的灵活性,即规范只是要求具备哪些功能,而实现请自由发挥


JVM运行时数据区域

1.模型图
题外话:为什么要划分区域和分类——为了方便管理

  • 线程私有:程序计数器、栈(虚拟机栈、本地方法栈)
  • 线程共享:方法区、堆
    在这里插入图片描述

2.程序计数器
当前线程执行的字节码的行号指示器。

  • 一块较小内存空间
  • 线程私有
  • 执行java方法,计数器值为字节码指令地址
  • 执行本地方法,计数器值为空
  • 唯一不存在内存溢出的区域(OOM)

3.Java虚拟机栈
Java方法执行的内存模型。一个线程对应一个线程栈,一个方法对应线程栈里面的一个栈帧,方法的调用到执行结束对应栈帧的入栈和出栈。

  • 虚拟机栈为线程私有
  • 栈帧的组成
    • 局部表量表
    • 操作数栈
    • 动态链接
    • 方法出口
    • ……
  • 局部变量表存储的数据类型
    • 基本数据类型
    • 引用数据类型(对象起始地址、对象句柄、间接指向对象的地址)
    • returnAddress(指向字节码指令的地址)
  • 存在两种异常
    • OutOfMemoryError
    • StackOverflowError
  • 分类
    • 固定大小栈
    • 可扩展栈
  • 大部分虚拟机都是可扩展栈(-Xss:虚拟机启动时,每个java线程栈的大小)
  • 固定大小栈超过最大栈深请求会报错StackOverflowError;可扩展栈扩展时申请不到足够内存会报错OutOfMemoryError

4.本地方法栈
本地方法的执行模型。

  • 与虚拟机栈类似,只不过执行的方法是本地方法(-Xoss:虚拟机启动时,每个本地线程栈的大小)
  • 虚拟机规范中没有规定其语言、数据结构、执行方式,虚拟机可自由实现
  • hotspot虚拟机栈将本地方法栈与虚拟机栈合二为一(-Xoss这个参数在hotspot中无效)

5.堆
存储对象实例和数组。

  • 所有线程共享
  • 内存占用最大,几乎所有的对象实例和数组都存放在这里(几乎:因为即时编译器和逃逸分析成熟,使得有了栈上分配和标量替换等优化技术,使得对象数组可以分配在栈上)
  • 垃圾回收器管理的主要区域(GC只针对线程共享,方法区的GC收效甚微)
  • 区域划分:新生代+ 老年代 [ + 永久代 ]
  • 虽说这个区域线程共享,但也存在线程私有的概念(线程私有分配缓存区TLAB thread local allocation buffer ;-XX:+/-UseTLAB来决定是否使用TLAB),是为了让同一线程需要存储的对象尽可能放在物理位置相近的地方,使得使用和回收的时候更加方便和高效
  • 虚拟机堆在物理内存上可以不连续,但在逻辑内存行一定连续
  • 分类
    • 固定大小堆
    • 可扩展堆(-Xmx:虚拟机运行时内存最大大小 -Xms:虚拟机启动时内存大小)
  • 当对象分配不到足够内存空间就会出现OutOfMemoryError

6.方法区
存储已被虚拟机加载的类信息、常量、静态变量、即时编译代码等数据。

  • 线程共享
  • 有些虚拟机将方法区用永久代的方式实现,为了简化内存管理(-XX:MaxPermSize 永久代的最大占用内存大小)。永久代意味着内存上限是虚拟机规定的永久代内存大小,而没有永久代意味着本地内存大小是上限
  • JRockit和J9就没有永久代的概念
  • hotspot的方法区现在已经部分使用虚拟机内存部分使用本地内存了,如jdk1.7将字符串常量池移到了本地内存中
  • 方法区物理上可以不连续,但逻辑上连续
  • 方法区的GC主要针对常量池和类卸载,而类卸载的条件相当苛刻
  • 方法区可以先择不进行垃圾回收
  • 无法申请到足够内存是会出现OutOfMemoryError
  • 运行时常量池
    方法区的一部分,存储字面量和符号引用,具体细节规范内没有明确写出。
    • 区别于class文件中的常量池,它具有动态性,会随着程序执行而新增常量
    • String的intern()方法就是从常量池直接找对象,存在就返回,不存在创建并返回
    • 受方法区内存大小的限制,也存在OutOfMemoryError

7.直接内存
不属于虚拟机运行时数据区,是物理内存。

  • 存在OutOfMemoryError
  • 如果内存溢出,dump文件很小,且程序直接或者间接使用了NIO,则考虑直接内存OOM
  • jdk1.4提出了NIO(new io),基于通道、缓存,实现将对象实例直接分配在虚拟机内存外的本地内存中,并在堆中创建一个DirectByteGBuffer对象来引用直接内存中存的对象实例,避免了在堆和直接内存中来回的对象复制
  • Xmx越大,直接内存越小

JVM内存异常

1.内存异常

  • 内存溢出:内存不够用
  • 内存泄漏:垃圾回收机制无法回收的dead对象

2.Java虚拟机中存在的两种内存异常

  • OutOfMemoryError:申请内存时,内存不够用
    • 在堆中,新生成一个对象需要分配内存。一般情况下(除非对象特别大,直接分配在老年代),先从新生代分配内存空间。如果内存不够用,则进行垃圾回收;如果GC后还不够用(扩容,扩容失败或扩容后还不够用),则抛异常。
    • 在栈中,申请栈空间时内存不够用,则先进行扩容,扩容失败或扩容后仍然不够用,则抛异常。
    • 方法区类似于堆。
  • StackOverflowError:超过最大栈深
    • 栈帧大小超过该线程栈剩余空间大小且无法扩容时,抛异常。

3.JVM的“内存”

  • JVM的“内存”总空间 = 栈大小 + 方法区大小 + 堆大小 + 程序计数器大小(相比其它来说过于小,可忽略)
  • 基本的内存调控思路
    • 总的物理内存一定,若JVM内存越大,直接内存就越小
    • 堆空间越大,栈空间越小
    • 希望线程数多,则减小最大堆大小以扩大栈空间大小,同时减少栈容量即每个线程占用空间
    • 方法区也可以调小来扩大其它内存空间

参数

  • -Xms:堆的最小值
  • -Xmx:堆的最大值
  • -XX:+HeapDumpOnOutOfMemoryError:让虚拟机在内存溢出时dump出内存块的转储快照以便事后分析
  • -Xss:线程栈的初始大小
  • -Xoss:本地线程栈的初始大小
  • -XX:MaxDirectMemorySize:指定直接内存容量,如果不指定默认与最大堆内存一样大
  • -XX:MaxPermSize:永久代的最大值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值