image.png

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区
    • 我们所通常说的“栈”通常就是指这里讲的虚拟机栈,或 者更多的情况下只是指虚拟机栈中局部变量表
    • 局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始 地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。

1.1 程序计数器

image.png
作用:

  • 是记住下一条jvm指令的执行地址
  • 多线程时为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器

特点: 是线程私有的 不会存在内存溢出

线程是私有的就是说我们每个线程都有自己的一个程序计数器

计数器在物理机器上是基于寄存器实现的

1.2 虚拟机栈

  • Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同

栈是我们的线程运行的时候所需要的内存空间

  • 每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

image.png
程序在运行时候,每个方法都代表一个栈桢,我们的栈帧可以就是每个方法运行所需要的内存

方法一运行时的栈帧1入栈携带信息,让栈帧2也入栈,当方法执行完毕后就出栈,如下所示:
image.png
小结
image.png

在栈顶部的栈帧就是活动栈帧

1. 垃圾回收是否涉及栈内存?

  • 不涉及,栈内存就是一次次的方法调用产生的栈帧内存,栈帧内存在每一次方法调用结束后都会弹出栈也就是会被自动回收掉,不需要垃圾回收来管理栈内存,垃圾回收是回收我们的堆内存

2. 栈内存分配越大越好吗?

  • 栈内存在运行时可以通过一个虚拟机的参数指定大小-Xxs size如果不指定这个参数默认大小是1024kbimage.png

栈内存分配的越大,线程数就会变少,栈划分的大只是会能进行更多的方法递归调用

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

  • 如果方法内局部变量没有逃离方法的作用访问(逃离可以是我们需要的变量是传参进来的,或者有return),它是线程安全的
  • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全 如下:

image.png

栈内存溢出

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

    在《Java虚拟机规范》中,对这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚 拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展[2],当栈扩 展时无法申请到足够的内存会抛出OutOfMemoryError异常

java.lang.StackOverflowError异常,全部列举如下:
https://blog.csdn.net/dabusiGin/article/details/105101757

  1. 不恰当的递归方法;
  2. 两个方法循环调用;
  3. 循环调用构造方法;
  4. 程序自动循环调用toString()方法;

oom异常
Java 堆溢出,其原因:

  • 无法在 Java 堆中分配对象
  • 应用程序保存了无法被GC回收的对象。
  • 应用程序过度使用 finalizer。

    1.3 本地方法栈

    image.png
    java不能直接操作系统,需要用c/c++来进行
    而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。

    栈内存溢出

    与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失 败时分别抛出StackOverflowError和OutOfMemoryError异常。

    1.4 堆

    Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建
    对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块

    此内存区域的唯一目的就是存放对象实例 在《Java虚拟机规范》中对Java堆的描述是:“所有 的对象实例以及数组都应当在堆上分配”

Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”

  • 将Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。

image.png
new出来的对象放在堆内存中,局部变量里放的是指向这个内存放在堆内存的地址存在在栈中

1.4.1 堆内存溢出

如果在Java堆中没有内存完成实例分配,并且堆也无法再 扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

1.5 方法区

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

方法区与永久代

方法区与永久代并不是等价的 因为仅仅是当时的HotSpot虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区而已 其他虚拟机厂商并不是这样做的
但现在回头来看 当年使用永久代来实现方法区的决定并不是一个好主意,这种设计导致了Java应用更容易遇到内存溢出的问题
而到了 JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Metaspace)来代替,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中

总结一下现在jdk8以后就没有永久代了,把永久代的内容全部转到元空间(在本地内存中)

数据进入方法区并不意味着永久存在了 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载

image.png

image.png

方法区内存溢出

根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。

  • 1.8 以前会导致永久代内存溢出

演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space * -XX:MaxPermSize=8m

  • 1.8 之后会导致元空间内存溢出

演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MaxMetaspaceSize=8m

1.6 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
image.png
运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性:

Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常 量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的 intern()方法。

内存异常

因为常量池属于方法区自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常
运行时常量池的一个重要组成部分**StringTable**
image.png
StringTable的位置
image.png
StringTable的垃圾回收现象

1.7 直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分

不属于Java来管理,它是属于操作系统的

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

    内存溢出

    也可能导致OutOfMemoryError异常出现
    直接内存的分配不会受到Java堆大小的限制 但是受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制 一般服务 器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得 各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现 OutOfMemoryError异常。

image.png
使用DirectBuffer加入直接内存后系统内存会划分出一块能与Java共享的内存空间,加快传输速度
image.png
分配和回收原理

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