Java内存初探


JVM的内存区域都有哪些

主要内容整理自JavaGuide;

JDK1.8与之前的是有些区别的。

JDK1.6

JDK1.8

总览

可以分两类:

  • 线程私有的
    • 虚拟机栈
    • 本地方法栈
    • 程序计数器
  • 线程共享的
    • 方法区
    • 直接内存(非运行时数据区的一部分)

程序计数器

自己理解,一句话概括:

同单片机/计组等一样,存储程序执行的指令/字节码位置
多线程下每个线程都各自有一个私有的,存储线程切换时的位置方便切换回来继续执行


简单理解可以认为是存储当前程序执行的行号的一个比较小的内存区域。

也就是指示当前执行指令的计数器。字节码解释器通过改变程序计数器中的值,选取下一条要执行的指令,控制指令的执行顺序,用于顺序、循环、判断、异常处理。

还有就是多线程情况下,每个线程都会有一个私有的程序计数器,当进行线程切换时记录当前线程执行到的位置,等切换回来之后再从这个地方继续执行。

从上面的介绍中我们知道程序计数器主要有两个作用:
1.字节码解释器通过改变程序计数器来==依次读取指令==,从而实现代码的流程控制,如︰顺序执行、选择、循环、异常处理。
2.在多线程的情况下,程序计数器用于==记录当前线程执行的位置==,从而当线程被切换回来的时候能
够知道该线程上次运行到哪儿了。

==程序计数器生命周期和线程一样==,随着线程创建而创建,随着线程结束而死亡。

程序计数器是唯一一个不会出现OutOfMemoryError 的内存区域。

Java虚拟机栈

同样是线程私有的,同样是生命周期同线程。

Java方法执行的内存模型,方法调用传递的数据都是通过栈传递。

Java内存粗糙区分:

  • 栈内存,Stack指得就是Java虚拟机栈,或者说Java虚拟机栈中的局部变量表。
    • 局部变量表
      • 编译器可知的数据类型(基本数据类型)
      • 对象引用(是引用,而不是对象)
  • 堆内存,Heap。

Java虚拟机栈会出现的两种异常:

  • Java虚拟机栈,不允许动态扩展:线程请求栈深度,超过最大深度,就会报StackOverFlowError
  • Java虚拟机栈,允许动态扩展:线程请求栈深度,内存用完了,没法再扩展了,就会报OutOfMemoryError;

方法/函数调用过程中,每次方法调用就会压入Java虚拟机栈一个栈帧;每次方法返回就会弹出一个栈帧(正常return或者抛出异常)

本地方法栈

作用和Java虚拟机栈一毛一样,但是是用与Native方法的一个栈。

简单来说,Native方法是指Java调用非Java代码(如C++)方法。

HotSpot虚拟机中这两个栈合为一个了。

https://ask.csdn.net/questions/235010

JVM是虚拟机,总的来说是一种标准规范,虚拟机有很多实现版本。主要作用就是运行java的类文件的。

而HotSpot是虚拟机的一种实现,它是sun公司开发的,是sun jdk和open jdk中自带的虚拟机,同时也是目前使用范围最广的虚拟机。

HotSpot,顾名思义,它是基于热点代码探测的,有JIT即时编译功能,能提供更高质量的本地代码。

二者区别是一个是标准,一个是实现方式。

  • Java字节码方法:Java虚拟机栈
  • Native方法(非Java方法):本地方法栈

  • 线程共享的
  • 虚拟机启动时创建
  • 最大的一块内存
  • 存放对象实例及数组

Java堆是垃圾收集器管理的主要区域,所以说也被称为GC堆(Garbage Collected Heap)。

垃圾回收的角度看,现在收集器基本采用分代垃圾收集算法

  • 分代垃圾收集算法,细分为:
    • 新生代
    • 老年代
  • 进一步细分
    • Eden 空间
    • From Survivor 空间
    • To Survivor 空间
  • 细分目的
    • 更好的回收内存
    • 更快的分配内存

image-20210419223201995

eden , s0 , s1 是新生代,tentired为老年代。

1
2
graph 
在eden区新建对象 --1次新生代垃圾回收--> 对象存活进入s0/s1,年龄增加1 --年龄到达阈值,默认为15--> 晋升到老年代中

其中阈值可以通过 -XX:MaxTenuringThreshold 来设置。

方法区

主要用来存储:

已被虚拟机加载的:

  • 类信息
  • 常量
  • 静态变量
  • 即时编译器编译后的代码等

方法区与永久代

方法区是Java规范中的说法。

而在HotSpot虚拟机中,实现是永久代。

JDK1.8之前是永久代(PermGen),JDK1.8之后是元空间(MetaSpace)。

为什么要将永久代换为元空间?

永久代有一个JVM本身设置的固定大小的上限,超出它之后会报OutOfMemoryError

而元空间使用的是直接内存,不会报OutOfMemoryError。

这是其中原因之一,其他还有很多底层原因。

运行时常量池

https://blog.csdn.net/wangbiao007/article/details/78545189

运行时常量池中存放的东西:

image-20210419232528196

运行时常量池

  • 在JDK1.7之前是在方法区内
  • 在JDK1.7之后就放到堆Heap中了

直接内存

不是JVM运行时数据区的一部分

也不是JVM规范中定义的内存区域

直接内存是在Java堆外的、直接向系统申请的内存区间

被频繁使用,也有可能导致OutOfMemoryError异常。

优点:
通过DirectByteBuffer对象操作Native内存通常,访问直接内存的速度会优于Java堆。即读写性能高。

缺点

  • 分配回收成本高
  • 不受JVM内存管理回收

文章作者: SongX64
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 SongX64 !
  目录