简述JVM内存模型及垃圾回收

JVM内存模型

运行时数据区->方法区、堆、虚拟机栈、本地方法栈、程序计数器

程序计数器

  • 它是一块很小的内存空间,几乎可以忽略不计。也是运行速度最快的存储区域。

  • 在JVM规范中,每个线程都有它自己的程序技术器,是线程私有的,生命周期与线程的生命周期保持一致。

  • PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。

  • 它是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

  • 问:使用PC寄存器存储字节码指令地址有什么用?(为什么使用pc寄存器记录当前线程的执行地址?)

    答:因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪里继续开始执行。

    JVM的字节码解释器就需要通过改变pc寄存器的值来明确下一条应该执行什么样的字节码指令。

GC只存在方法区和堆中!

栈是运行时的单位,堆是存储的单位。

虚拟机栈:

  • 栈中的数据都是以栈帧的格式存在的。(在这个线程上正在执行的每个方法都各自对应一个栈帧)

  • 每个栈帧中存储局部变量表,操作数栈、动态链接、方法返回地址、一些附加信息

  • 局部变量表:(用来存放各种变量),最基本的存储单元是Slot(变量槽),槽位可以重复利用

  • 操作数栈(表达式栈):主要用于保存计算结果的中间结果,同时作为计算过程中变量的存储空间

  • 动态链接:指向运行时常量池的方法引用,作用就是为了将这些符号引用转换为调用方法的直接引用

  • 方法返回地址:(方法正常退出(正常return)或者异常退出(遇到异常)的定义)

    区别在于:通过异常完成出口退出的不会给他的长层调用者任何的返回值。

  • 局部变量表中的变量是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都会被回收。

本地方法栈:

  • java虚拟机栈用于管理java方法的调用,而本地方法栈用于管理本地方法的调用。也是线程私有的。
  • 允许被实现成固定或可动态扩展的内存大小,也会出现SOF,OOM
  • 本地方法栈使用C语言实现的。
  • 并不是所有的jvm都支持本地方法。

堆:

  • 一个jvm实例只存在一个堆内存,堆也是java内存管理的核心区域

  • java堆区在jvm启动的时候即被创建,其空间大小也就确定了,是jvm管理的最大一块内存空间

    =>堆内存的大小是可以调节的

  • 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的

  • 所有的线程共享java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)

  • “几乎”所有的对象实例都在这里分配内存

  • 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置

  • 在方法结束之后,堆中的对象不会马上被移除。仅仅在垃圾收集的时候才会被移除。

  • 堆 是GC(垃圾回收器)执行垃圾回收的重要区域

  • 堆空间

    • 内存设置大小参数:

      “-Xms” 起始内存 ->默认:物理内存大小/64

      “-Xmx" 最大内存 ->默认:物理内存大小/4

      通常将这两个参数配置相同的值,

      目的为了能够在java回收机制清理完内存之后不需要重新分隔堆区的大小,从而提高性能

    • 内存逻辑:

      java7 及之前分为: 年轻代 + 老年代 + 永久区

      java8 及之后分为: 年轻代 + 老年代 + 元空间

      默认:新生代 : 老年代 = 1: 2

    • 年轻代又分为:Eden区,Survivor0区和Survivor1区(有时也叫from区和to区)

      几乎所有的java对象都是在Eden区被new出来的。

      绝大部分的java对象的销毁都在新生代进行了。新生代80%的对象都是“朝生夕死”的

  • 垃圾回收GC

    分类:

    jvm在进行回收的时候,并非每次都是一起回收的。大部分回收的都是指新生代。

    • 部分收集:

      新生代收集(Young GC):只针对新生代进行垃圾收集

      老年代收集(Old GC):只针对老年代的垃圾收集

      混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集

    • 整堆收集(Full GC):收集整个java堆和方法区的垃圾收集

    *过程(非常重要):

    1. 首先在Eden区new对象,当伊甸园区满了之后,就会触发young GC,将Eden区中的不再被其他对象所引用的对象进行销毁。将剩余的对象放入到幸存者0区,
    2. 再new对象,Eden区再满之后,再次触发Young GC,进行无用对象销毁,将eden区和s0区剩余的对象放入到s1区。
    3. 如此反复,默认阈值是15,就会将剩余对象放入到老年代区。
    4. 当老年代区内存不足时,就会触发Old GC,进行内存清理。
    5. 最后当老年区执行了Old GC 之后发现依然无法进行对象的保存,就会产生OOM异常。

方法区:

  • 其实方法区就是永久代(元空间)。

  • 方法区与堆的关系呢,就像台湾与大陆的关系。逻辑上是一体的,但是也是自己独立存在的。

  • 方法区和堆一样,是各个线程共享的内存区域

  • 方法区在jvm启动的时候被创建,并且它的实际物理内存空间中和堆区一样都是可以不连续的。

  • 方法区的大小,跟堆空间一样,可以选择固定大小或者可拓展。

  • 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误。

  • 关闭jvm就会释放这个区域的内存

  • 方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型

    只要常量池中的常量不被任何地方引用,就可以被回收

  • 存储:已被jvm加载的 类型信息、运行时常量池、静态变量、JIT代码缓存、域信息、方法信息

  • 为什么永久代会被元空间所替代

    • 为永久代设置空间大小是很难确定的

      在某些场景下,如果动然加载类过多,容易产生永久代的oom。比如某个实际web工程中,因为某个实际web工程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误。

      而元空间与永久代最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

      因此,在默认情况,元空间的大小仅受本地内存限制。

    • 对永久代进行调优是很困难

OOM、 SOF

StackOverflowError:栈溢出,常见于递归

OutOfMemoryError:(OOM)内存溢出

java虚拟机规范允许java栈的大小是动态的或者是固定不变的。

->如果采用固定大小的java虚拟机栈,那每一个线程的java虚拟机栈容量可以在线程创建的时候独立选定。

如果线程请求分配的栈容量超过java虚拟机栈允许的最大容量,java虚拟机将会抛出SOF异常。

->如果java虚拟机可以动态扩展,并且尝试扩展的时候无法申请到足够的内存

​ 或者在创建新线程时没有足够的内存去创建对应的虚拟机栈,将会抛出OOM异常。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值