JVM - 内存区域与内存溢出

概述

对从事C和C++的程序员来说,在内存管理方面,他们既是拥有最高权利的人,也是从事最基础工作的“劳动人民”。

而对于Java程序员来说,JVM自动进行内存管理,程序员不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄露和内存溢出问题。

但是,正因为JVM帮我们管理了内存,一旦出现内存泄露或溢出问题,如果不了解虚拟机是怎么管理内存的,那么排查错误会成为一项异常艰难的工作

So, 小伙伴们,走起,让我带你们进入JVM的内存世界!

1. 运行时数据区域

JVM在运行时(运行时就是执行Java程序时)根据《Java虚拟机规范(Java SE 7)》的规定,会包括如下几个运行时数据区域

JVM - 内存区域与内存溢出_第1张图片

1.1 程序计数器(Program Counter Register)

或者叫:程序计数寄存器、PC寄存器,学过计算机组成原理应该就懂

a. 作用

  1. 程序计数器是一块较小的内存空间,可以看做是当前线程(Thread)所执行的字节码的行号指示器

    在JVM概念模型中(各种JVM可能并不依照此开发),解释器(Interpreter)就是通过改变这个计数器的值来选取下一条需要执行的字节码指令

  2. 为了在线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器。这种内存区域称为“线程私有”的内存

  3. 线程正在执行Java方法时,计数器记录的是虚拟机字节码指令的地址;如果执行的是Native方法,则计数器值为空(Undefined)

b. Error

这块内存是JVM规范中唯一没有规定任何OutOfMemoryError的区域

1.2 虚拟机栈(Virtual Machine Stacks)

也是线程私有的,其生命周期和线程一样。每个线程肯定有一个虚拟机栈,除非此线程没有执行Java方法(基本上不可能)

a. 作用

虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的时候都会创建一个栈帧(Stack Frame),其中存储了:

  • 局部变量表

    • 存放了编译器就可知的:各种基本数据类型(8个基本数据类型)、对象引用、returnAddress类型(指向一条字节码指令地址)
    • 局部变量表所需的内存大小在编译期就完成了分配,也就是说当进入一个方法时,此方法需要在栈帧中分配多大的局部变量表空间时完全确定的,运行期不会改变
  • 操作数栈

  • 动态链接

  • 方法出口等

方法从调用到执行完成的过程,就对应了,一个栈帧在虚拟机栈中的入栈和出栈的过程

b. Error

有两种异常:
1. 如果线程请求的栈深度大于JVM所允许的深度,将抛出StackOverflowError异常
2. 如果栈扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常

1.3 本地方法栈(Native Method Stack)

a. 作用

作用和虚拟机栈基本一样,只不过这里为Native方法服务
JVM规范没有强制规定本地方法栈中的方法使用的语言、使用方式、数据结构,所以具体JVM不同实现

HotSpot虚拟机直接把虚拟机栈和本地方法栈合二为一了

b. Error

如1.2的虚拟机栈一样

1.4 Java堆(Java Heap)

基本上Java堆是虚拟机管理的内存中最大的一块。是被所有线程共享的一块区域,在虚拟机启动时创建,通过参数“-Xmx和-Xms”控制

a. 作用

所有对象实例和数组都要在堆上存放(例外的情况在我另一篇博客有描述JVM - JIT编译器 - 6.1)
Java堆是垃圾回收器管理的主要区域

b. 分类

下面是一些具体的细分,但是不论如何分类,其存储的仍然是对象实例,进一步划分的目的是为了更好的回收、更快的分配内存

1. 从内存回收的角度看

由于现代GC基本都采用分带收集算法,所以Java堆还可以细分为:

  1. 新生代
  2. 老年代

再细分一下还可分为:

  1. Eden空间
  2. From Survivor空间
  3. To Survivor空间

2. 从内存分配角度看

线程共享的Java堆中可能划分出过个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)

c. Error

如过堆中内存不够继续进行实例分配,且堆也无法再扩展时,将会抛出OutOfMemoryError

1.5 方法区(Method Area)

方法区和Java堆一样,被所有线程共享

a. 作用

用于存储已被虚拟机加载的

  1. 类信息
  2. 常量
  3. 静态变量(类变量 class statics)
  4. 即时编译器编译后的代码等

虽然JVM规范把方法区描述为堆的一个逻辑部分,但是它确有一个别名叫Non-Heap,目的应该是与Java堆区分开来

b. 永久带

对于使用HotSpot VM的程序员来说,很多人把方法区称之为“永久带(Permanent Generation)”

为什么叫永久带?如1.4中所说,按内存回收的角度,有新生代、老年代,所以就有了这里的“永久带”。另外,其它虚拟机是没有永久带这个概念的

本质上两者不等价,仅仅是因为HotSpot团队把GC分带收集扩展到了方法区(或者说用永久带这种方式来实现方法区),这样的话GC就可以像管理Java堆一样管理这部分内存,省去了为方法区编写内存管理代码的工作

1. 坏处

如何实现方法区JVM规范并没有强制规定,但是现在看来“永久带”并不是一个好主意

  1. 更容易遇到内存溢出问题
    因为有参数“-XX:MaxPermSize”的上限限制,其它虚拟机只要不达到进程可用内存上限,例如32系统的4GB,就不会出现内存溢出
  2. 有极少数方法(如String.intern()),会因此导致在不同的JVM下有不同表现

2. 现状

c. Error

你可能感兴趣的:(jvm,内存,内存溢出)