本人个人博客网站,欢迎访问:学教府
一、简述
- 介绍:如果有了解 C/C++,以及 Java 语言的朋友,肯定知道它们之间最大的差别的就是对于内存的回收。用 C/C++ 时,自己创建的对象得自己回收内存,而 Java 则是因为其自带的垃圾收集器,将开发者解放出来。犹记得当初大学学 C 语言,讲到指针的那一节,就感觉指针是无所不能,各种灵活,不过如果用不好就容易飘。本篇博客主要简单介绍一下 Java 虚拟对于内存的划分,以及大概作用。
二、内存运行时数据区
-
这张图片展示的就是 JVM 对于内存的经典划分,主要分为了程序计数器、虚拟机栈、本地方法栈、堆和方法区,同时还有运行时常量池和直接内存
-
程序计数器:
- 线程私有(每条线程独立有一个),可以看成当前线程所执行的字节码的行号指示器。
- 如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地方法,这个计数器值则应为空。
- 此内存区域是唯一没有 OutOfMemoryError 情况的区域
-
虚拟机栈: 通俗的讲,一个线程有一个虚拟机栈,这个栈就是一个中间容器,帮助线程在执行过程中完成各项处理。其中栈里面的一个基础单位就是栈帧,一个栈帧可以简单的对应一个方法,在进入方法后会有局部变量等数据,此时就创建一个栈帧来保存这些信息(比如局部变量表、操作数栈、动态连接、方法出口等信息)。栈是先进后出的数据结构。虚拟机栈特点如下:
- 线程私有,生命周期与线程相同
- 一个方法的执行对应一个栈帧的入栈和出栈过程
- 局部变量表存放 8 种基本数据类型、对象引用和 returnAddress 类型
- long 和 double 占用两个变量槽,其他类型是只占一个
- 两类异常状况:
① 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverFlowError 异常
② 如果 Java 虚拟机栈容量可以动态扩展(HotSpot 不可以),当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError 异常
-
本地方法栈:本地方法栈和虚拟机栈基本一样,区别是 虚拟机栈为虚拟机执行 Java 方法(字节码)服务,而本地方法栈是为虚拟机使用到的本地方法服务 。异常和虚拟机栈的一致。本地方法解释可以查看——详解native方法的使用
-
Java 堆:堆是虚拟机管理的内存中最大的一块,这块地方存放了 Java 中几乎所有的对象实例(有容乃大)。因为这块地方大,并且发生的故事多,所以内存的回收(垃圾收集器)都发生在这儿,因此也被叫为 “GC堆”。有四处特点如下:
- 虽然堆是线程共享的,但是在共享的地方也会为线程划分私有的分配缓冲区
- Java 堆在内存物理上可能不连续,但是对于我们使用来说,逻辑上是连续的
- 虚拟机实现堆是可扩展的,通过参数 -Xmx 和 -Xms 设置
- 在堆中没有内存可以进行实例分配时,也无法扩展,则会抛出 OutOfMemoryError 异常
-
方法区:与 Java堆 一样是线程共享的,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。注意:经常会将“永久代”和方法区混淆,他们的区别是 方法区是 JVM 的一种规范,而永久代是对这一种规范的实现。查看:方法区和永久代区别。特点如下:
- 在 jdk7 中已经将字符串常量池从方法区移除,并在Java堆中开辟一块新的区域存放字符串常量池
- 在 jdk8 中将永久代去除了,使用元空间来实现,元空间的内存区域使用本地内存
- 如果方法区无法满足新的内存分配需求时,将抛出 OutOfMemoryError 异常
-
运行时常量池:运行时常量池是方法区的一部分,存放编译器生成的各种字面量和符号引用。特点如下:
- 具备动态性,常量池的数据不是都在编译器存入的,还可以在运行时添加,比如 String 的 intern() 方法
- 常量池无法申请到内存时抛出 OutOfMemoryError 异常
- 对于各个版本方法区的区别如表格 1-1
-
直接内存:直接内存不是虚拟机运行时数据的一部分,其分配不会受到 Java 堆大小的限制。
- 在 JDK1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,在一些场景能显著提高性能
- 也可能出现 OutOfMemoryError 异常
表格 1-1:
jdk1.7以前 | jdk1.7 | jdk1.8 |
---|---|---|
字符串常量池在方法区中,实现为永久代 | 字符串常量池在堆中,实现为永久代 | 字符串常量池在堆中,实现为元空间 |
深入理解Java虚拟机第三版》