本系列笔记主要基于《深入理解Java虚拟机:JVM高级特性与最佳实践 第2版》,是这本书的读书笔记。

概述

Java 虚拟机为程序员分担了很多内存管理的工作,不再像 C/C++ 那样容易出现内存泄漏和内存溢出问题了,也正是这样,导致一旦出现了内存泄漏和溢出方面的问题,就难以排查。因此一个优秀的 Java 程序员应该对 Java 虚拟机有充足的了解,JVM 是你的必修课。

运行时数据区域

根据《Java虚拟机规范(Java SE 7版)》,JVM 所管理的内存区域的划分如下:

每个内存区域都有各自的用途,以及创建和销毁时间,有的区域会随着虚拟机的启动而存在,有的区域依赖用户线程而建立和销毁,接下来一次介绍这些内存区域。

程序计数器

程序计数器(Program Counter Register)是线程私有的内存区域,是当前线程所执行的字节码的行号指示器,是一块较小的内存空间。

字节码解释器(java源码编译成字节码,运行时解释成机器码执行)工作时,就是通过改变程序计数器的值,来选取下一条要执行的字节码。分支、循环、跳转等都依赖程序计数器执行。

当线程执行 Java 方法时,程序计数器记录的是正在执行的虚拟机字节码指令地址;当线程执行 Native 方法时,程序计数器的值为空(Undefined)。程序计数器是唯一一个在 Java 虚拟机规范中没有规定 OutOfMemoryError 的内存区域。

Java虚拟机栈

Java 虚拟机栈(Java Stack)也是线程私有的内存区域,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用以存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从开始调用到执行完成的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表存放了编译期可知的各种基本数据类型(boolean byte char short int long double float)、对象引用,以及 returnAddress 类型(指向一条字节码指令的地址)。局部变量表所需内存空间在编译期间完成分配,当进入一个方法时,需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表大小。

在 Java 虚拟机规范中,Java 虚拟机栈有可能会出现两种异常:StackOverflowError 和 OutOfMemoryError。如果线程请求的栈深度大于虚拟机栈的深度,则会 StackOverflowError。如果虚拟机栈动态扩展时申请不到足够的内存,则会 OutOfMemoryError。

本地方法栈

本地方法栈(Native Method Stack)与 Java 虚拟机栈的作用一样,是线程私有的,区别就是 Java 虚拟机栈为执行 Java 方法服务,本地方法栈为 Native 方法服务,虚拟机规范并没有对本地方法栈做强制规定,在 HotSpot 虚拟机中把本地方法栈和虚拟机栈合二为一了。此内存区域也会抛出 StackOverflowError 和 OutOfMemoryError。

Java堆

Java 堆(Java Heap)是一块被所有线程共享的内存区域,同时也是 Java 虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

Java 堆是垃圾收集器管理堆主要区域,也被称做“GC堆”。现在垃圾收集器基本都采用分代收集算法,所以从内存回收角度看,Java 堆还可以细分为:新生代和老年代;新生代可以再细分为 Eden 空间、From Survivor 空间、To Survivor空间。
如下图所示:

从内存分配角度看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。虚拟机为新生对象分配内存时,为每个线程在 Java 堆 中预先分配一小块内存,称做本地线程分配缓冲(TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配,只有 TLAB 用完并分配新的 TLAB 时,才会同步锁定(为了并发情况下的线程安全)。

Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果堆中没有足够内存完成实例分配,并且也无法扩展时,会抛出 OutOfMemoryError。

方法区

方法区(Method Aera)与 Java 堆一样,也是线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在虚拟机规范中,它被描述为堆的一个逻辑部分,但实际它应该要与 Java 堆区分开来。

从分代收集的角度看,方法区也被称做永久代(Permanent Generation),实际上两者并不等价,只是因为 HotSpot 虚拟机使用永久代实现了方法区,对于其他虚拟机(JRockit、IBM J9)是不存在永久代概念的。

使用永久代实现方法区,更容易出现内存溢出问题(永久代有 -XX:MaxPermSize 的上限),所以在 JDK1.8 中,HotSpot 就取消了永久代(JEP122),取而代之的是元空间(MetaSpace),元空间是方法区新的实现,而且使用的是本地内存不是虚拟机内存。原先永久代中类的元信息会放入元空间,类的静态变量和常量会放入 Java 堆。

永久代也并不是指真的“永久”存在,只是说这部分内存回收(常量池回收和对类型的卸载)的成绩难以令人满意,条件也非常苛刻。

方法区会有 OutOfMemoryError 异常。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本信息、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中,另外翻译出来的直接引用也会存储在这个区域中。

这个区域另外一个特点就是动态性,Java并不要求常量就一定要在编译期间才能产生,运行期间也可以在这个区域放入新的内容,String.intern()方法就是这个特性的应用。此区域有 OutOfMemoryError 异常。

JDK1.6及之前,常量池是位于方法区中的,但在JDK1.7的时候常量池挪到了堆内存,也就是常量池和对象共享 Java 堆,所以在 Java7 以后,常量池就不在方法区分配了,而是在 Java 堆 中分配。

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。但这部分区域被频繁的使用,也可能导致 OutOfMemory 异常出现。

JDK1.4 中加入了 NIO,这是一种基于通道(Channel)和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样显著提高了性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。显然,本机直接内存的分配不会受到 Java 堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM、SWAP区)大小以及处理器寻址空间的限制。在配置虚拟机参数时,会根据实际内存配置 -Xmx 等,但经常忽略直接内存,使得各个内存区域总和大于了物理内存,从而导致动态扩展时出现 OutOfMemoryError。

JVM探秘:Java内存区域的更多相关文章

  1. JVM之Java内存区域

    JVM之Java内存区域 世界上并没有完美的程序,但我们并不因此而沮丧,因为写程序本来就是一个不断追求完美的过程. 一.JAVA内存区域 谈及JAVA虚拟机运行时数据区域就不得不祭出这张经典的图了: ...

  2. 学习jvm(一)--java内存区域

    前言 通过学习深入理解java虚拟机的教程,以及自己在网上的查询的资料,做一个对jvm学习过程中的小总结. 本文章内容首先讲解java的内存分布区域,之后讲内存的分配原则以及内存的监控工具.再下来会着 ...

  3. 深入理解JVM - 1 - Java内存区域划分

    作者:梦工厂链接:https://www.jianshu.com/p/7ebbe102c1ae来源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处. Java与C++之间有一堵 ...

  4. 理解JVM之Java内存区域

    Java虚拟机运行时数据区分为以下几个部分: 方法区.虚拟机栈.本地方法栈.堆.程序计数器.如下图所示: 一.程序计数器 程序计数器可看作当前线程所执行的字节码行号指示器,字节码解释器工作时就是通过改 ...

  5. 史上最详细JVM,Java内存区域讲解

    本人免费整理了Java高级资料,一共30G,需要自己领取:传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q 运行时数据区域 JVM载执行Jav ...

  6. JVM:Java内存区域与内存溢出异常

    Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁时间,有些区域随着虚拟机进程的启动而存在,有些区域依赖用户线程的启动和 ...

  7. JVM(1) Java内存区域

    对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题.不过,也正是因为Java程序员把内存控制的权 ...

  8. 深入理解JVM(一)--Java 内存区域

    一.  运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. Java虚拟机所管理的内存将会包括以下几个运行时数据区域:               ...

  9. 深入理解JVM(二)Java内存区域

    2.1 C.C++内存管理是由开发人员管理,而Java则交给了JVM进行自动管理 2.2 JVM运行时数据区:方法区.堆(运行时线程共享),虚拟机栈.本地方法栈.程序计数器(运行时线程隔离,私有) 1 ...

  10. 【搞定Jvm面试】 Java 内存区域揭秘附常见面试题解析

    本文已经收录自笔者开源的 JavaGuide: https://github.com/Snailclimb ([Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识)如果觉得不错 ...

随机推荐

  1. React Native声明属性和属性确认

    属性声明 因为用React Native创建的自定义组件可以复用, 我们开发过程中可能一个项目组有多个人同时开发,其他同事可能会用到我们自定义的组件, 但是他们使用的时候很容易忘记使用某些属性,这时候 ...

  2. 17-1 djanjo进阶-路由,视图,模板

    一 路由系统进阶(urls.py) 动态路由 urls.py中通过正则表达式的分组匹配,捕获用户访问的url中的值,传递给视图函数1 分组匹配(通过圆括号): 相当于给视图函数传递 位置参数 例子: ...

  3. Facebook F8|闲鱼高级技术专家参会分享

    笔者代表闲鱼参加了Facebook在4月30日举行的为期二天的F8大会,地点加州.将会议概括和一些收获分享给大家.对国内开发者而言,Facebook的产品设计.社区.VR/AR等有一些借鉴意义:对海外 ...

  4. Best Open Source Software

    Best Open Source Software Open Source, Software, Top The promise of open source software is best qua ...

  5. 第三期 预测——Frenet 坐标

    Frenet坐标 在讨论过程模型之前,我们应该提到“Frenet Coordinates”,它是一种以比传统x,y笛卡尔坐标更直观的方式表示道路位置的方式. 用Frenet坐标,我们使用变量 s和d描 ...

  6. input上传图片并预览

    首先说一下input 大家都知道上传文件,图片是通过input 的file进行上传的. 1. 首先是样式 大家都知道input在HTML的代码为 <input type="file&q ...

  7. HTML5--语法

    一.标记方法 1.内容类型(ContentType)还是.text/html 2.声明:<!DOCTYPE html SYSTEM “about:legacy-compat”> 3.字符编 ...

  8. canvas+js实现验证码功能

    转载自:https://blog.csdn.net/qq_42463851/article/details/90755734<!DOCTYPE html> <html> < ...

  9. 2019-9-2-Visual-studio-创建项目失败vstemplate

    title author date CreateTime categories Visual studio 创建项目失败vstemplate lindexi 2019-09-02 12:57:38 + ...

  10. H3C ISDN DCC基本配置示例