Java 内存结构备忘录
本文详细描述了 Java 堆内存模型,垃圾回收算法以及处理内存泄露的最佳方案,并辅之以图表,希望能对理解 Java 内存结构有所帮助。原文作者 Sumith Puri,本文系 OneAPM 工程师编译整理。
下图展示了 Java 堆内存模型,以及运行在 Java 虚拟机中任意 Java 应用的 PermGen (内存永久保存区域),下面的比率展示了 JVM 各代类型允许的内存大小分配情况,所有的数据均适用于 Java 1.7 及以下版本。该图也被称为 Java 内存模型的“管理区(Managed Area)”。
Java 内存结构(Java 内存模型)
除此之外,还有一块堆栈区(Stack Area),可通过 -Xss 选项进行配置。该区域存储了所有线程的堆引用、本地引用、程序计数器寄存器、代码缓存以及本地变量。该区域也称为内存模型的本地区(Native Area)。
Java 内存模型(结构)的管理区
[Young Generation/Nursery] 伊甸园区(Eden Space)
所有新对象都首先在 Eden Space 创建。一旦该区达到由 JVM 设定的任意阈值,新生代垃圾回收机制(Minor GC)就会启动。它会首先清除所有的非引用对象,并将引用对象从 'eden' 与 'from' 区移至 'to' 幸存者区。垃圾回收一结束,'from' 与 'to' 的角色(名字)就会对换。
[Young Generation/Nursery] 幸存者 1 区 (From)
这是幸存者区的一部分。或者视为幸存者区中的一个角色。这儿就是之前垃圾回收中的 'to' 角色。
[Young Generation/Nursery] 幸存者 2 区 (To)
这也是幸存者区的一部分。也可以视为幸存者区中的一个角色。垃圾回收过程中的所有引用对象都会从 'from' 与 'eden' 区移至此处。
[Old Generation] 年老代区(Tenured)
根据阈值限定的不同,对象们会从 'to' 幸存者区移至年老代区。你可以使用 -XX:+PrintTenuringDistribution 检查阈值,该指令会按照年龄显示对象(占用的字节空间)。年龄是指对象在幸存者区内移动的次数。
其他重要的标记还有 -XX:InitialTenuringThreshold、-XX:MaxTenuringThreshold 与 -XX:TargetSurvivorRatio ,这些标记能帮你实现最佳的年老代区与幸存者区使用方案。
通过设置 -XX:InitialTenuringThreshold 与 -XX:MaxTenuringThreshold,可以指定年龄的最初值与最大值,而幸存者区 (To) 的使用率则由 -XX:+NeverTenure 与 -XX:+AlwaysTenure 决定。前者是指永远不将对象存储到年老代区,而后者恰恰相反,总是将对象存储到年老代区。
此处进行的垃圾回收是年老代垃圾回收(Major GC)。当堆空间已满或者年老代区占满时,就会触发 Major GC。此时,通常会由一个“停止一切(Stop-the-World)”事件或线程执行垃圾回收。此外,还有另一种称为全垃圾回收(Full GC)的垃圾回收机制,会涉及诸如永久内存区域。
与整体堆内存相关的另两个重要且有趣的标记是 -XX:SurvivorRatio 与 -XX:NewRatio,前者指定伊甸园区相对幸存者区的比率,后者指定年老代区相对新生代区的比率。
[Permanent Generation] 永久代区(Permgen space)
永久代区(Permgen)用于存储以下信息:常量池 (内存池),字段与方法数据及代码。
垃圾回收算法
串行 GC(Serial GC) (-XX:UseSerialGC): 针对年轻代与年老代的垃圾回收
该算法使用简单的“标记-清扫-压缩(mark-sweep-compact)”循环清理年轻代与年老代,适合内存占用较低、CPU 使用量较少的客户端系统。
并行 GC(Parallel GC) (-XX:UseParallelGC): 针对年轻代与年老代的垃圾回收
该算法使用 N 个线程(N 的值可以通过 ** -XX:ParallelGCThreads=N** 设定,N 同时代表垃圾回收占用的 CPU 内核数)。其中,年轻代垃圾回收会使用 N 个线程,而年老代只用一个线程。
并行 Old GC (-XX:UseParallelOldGC): 针对年轻代与年老代的垃圾回收
该算法对年轻代与年老代均使用 N 个线程,其他方面与并行 GC 完全一致。
并发 Mark and Sweep GC (-XX:ConcMarkSweepGC): 针对年老代的垃圾回收
顾名思义,CMS GC 会最小化垃圾回收所需的停顿时间。该算法最适于创建高响应度的应用,且只作用于年老代。它会创建多条垃圾回收的线程,与应用线程同时工作。垃圾回收的线程数量可以使用 -XX:ParallelCMSThreads=n 标记指定。
G1 GC (-XX:UseG1GC): 针对年轻代与年老代的垃圾回收 (将堆内存等分为大小相同的区块)
这是一种并行、并发、不断压缩的低停顿垃圾回收器。G1 是在 Java 7 中引入以取代 CMS GC 的,它会先将堆内存分为多个大小相等的区块,继而执行垃圾回收。通常,从活动数据最少的区块开始,因此以垃圾为先。
最常见的内存溢出问题
所有 Java 程序员都应该知道的最常见的内存溢出问题:
Exception in thread "main": java.lang.OutOfMemoryError: Java heap space( Java 堆内存)。这并不一定意味着内存泄露,也可能是分配的堆内存空间太小。此外,在运行时间较长的应用中,也可能是因为一个无意识的引用被指向堆对象(内存泄露)。即便是应用本身调用的 APIs,也可能保存着指向无依据对象的引用。而且,在大量使用终结器的应用中,对象们有时可能正排在终结队列中。当这样的应用创建高优先级的线程时,会导致越来越多的对象排在终结队列中,最终导致内存溢出。
- Exception in thread "main": java.lang.OutOfMemoryError: PermGen space(永久存储空间)。如果加载了很多类与方法,或者创建了很多字符串常量,特别是使用 intern() 方法进行创建(从 JDK 7 开始,interned 字符串就不再存储在 PermGen 中),这类错误就会出现。当出现这类错误时,打印的堆栈跟踪附近可能会出现如下文本:ClassLoader.defineClass。
- Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit (请求的数组大小超出 VM 限制)。当请求的数组大小超过可用的堆空间时,这类报错就会出现。这类错误通常归咎于编程错误,在运行时请求了极大的数组大小。
Exception in thread "main": java.lang.OutOfMemoryError:
request <s> bytes for <r>
,交换空间溢出?这是内存泄露最常见的根源。通常,当操作系统没有足够的交换空间,或另一个进程占用了系统中的所有可用内存,就会导致内存泄露。简而言之,由于空间用尽,堆内存无法提供所请求的空间大小。该信息中的 's' 代表失败的请求所需的内存大小(以字节为单位),而 'r' 代表内存请求的原因。在大多数情况下,此处的 'r' 是报告分配失败的源模块,有时也会是具体的原因。Exception in thread "main": java.lang.OutOfMemoryError: (Native method)()。该报错意味着一个本地方法遇到内存分配失败。问题的根源在于 Java Native Interface(Java 本地接口) 中存在的错误,而非 JVM 中运行的代码错误。若是本地代码不检查内存分配错误,应由会直接崩溃,而不会出现内存溢出。
内存泄露的定义
你可以将内存泄露看做一种疾病,而内存溢出错误为一种征兆。但不是所有的内存溢出错误都意味着内存泄露,不是所有的内存泄露都以内存溢出为征兆。
维基百科的定义:在计算机科学中,内存泄露是一种以如下方式发生的资源泄露——计算机程序错误地分配内存,导致不再需要的内存得不到释放。在面向对象的编程语言中,一个对象若存储在内存中,却无法由运行的代码获取,即为内存泄露。
Java 中常用的内存泄露定义
当不再需要的对象引用仍旧多余地予以保存,即为内存泄露。
在 Java 中,内存泄露是指对象已不再使用,但垃圾回收未能将他们视做不使用对象予以回收。
当程序不再使用某个对象,但在一些无法触及的位置该对象仍旧被引用,即为内存泄露。也因此,垃圾回收器无法删除它。该对象占用的内存空间无法释放,程序所需的总内存就会增加。久而久之,应用的性能就会下降,JVM 可能会耗尽所有内存。
在某种程度上,当无法再给年老区分配内存时,内存泄露就会发生。
内存泄露最常见的一些情况:
- ThreadLocal 变量
- 循环与复杂的双向引用
- JNI 内存泄露
- 可变的静态域(最为常见)
我建议结合使用 Visual VM 与 JDK,对内存泄露问题进行调试。
常见的内存泄露调试方法
- NetBeans 分析器
- 使用 jhat Utility
- 创建 Heap Dump
- 获取当下运行进程的堆内存柱状图
- 获取内存溢出错误的堆内存柱状图
- 监控在等待终结的对象数量
- 第三方内存调试器
调试内存泄露问题的常用策略或步骤:
- 确认征兆
- 启用详细的垃圾回收机制(verbose GC)
- 启用性能分析
- 分析堆栈跟踪
原文地址:https://dzone.com/articles/java-memory-architecture-model-garbage-collection
OneAPM for Java 能够深入到所有 Java 应用内部完成应用性能管理和监控,包括代码级别性能问题的可见性、性能瓶颈的快速识别与追溯、真实用户体验监控、服务器监控和端到端的应用性能管理。想阅读更多技术文章,请访问 OneAPM 官方博客。
Java 内存结构备忘录的更多相关文章
- Java内存结构、类的初始化、及对象构造过程
概述 网上关于该题目的文章已经很多,我觉得把它们几个关联起来讲可能更好理解一下.与其它语言一样,它在执行我们写的程序前要先分配内存空间,以便于存放代码.数据:程序的执行过程其实依然是代码的执行及数据的 ...
- java内存结构学习的一种打开方式
用Java开发已经四年,中途学了python,Scala,接触这些开发语言后,总感觉Java就像老奶奶裹脚——又臭又长.然,Java虐我千百遍,我待Java如初恋.聊起Java,不得不谈Java的内存 ...
- Java内存结构详解
Java内存结构详解 Java把内存分成:栈内存,堆内存,方法区,本地方法区和寄存器等. 下面分别介绍栈内存,堆内存,方法区各自一些特性: 1.栈内存 (1)一些基本类型的变量和对象的引用变量都是在函 ...
- JVM之--Java内存结构(第一篇)
最近在和同事朋友聊天的时候,发现一个很让人思考的问题,很多人总觉得JVM将java和操作系统隔离开来,导致很多人不用熟悉操作系统,甚至不用了解JVM本身即可完全掌握Java这一门技术,其实个人的观点是 ...
- java内存结构
Java的内存结构 JVM的内存结构主要有三大块:堆.方法区和栈.堆内存是JVM中最大的一块,由年轻代和老年代组成,而年轻代内存又被分为三部分,Eden空间.FromSurvivor空间和ToSurv ...
- java内存结构JVM——java内存模型JMM——java对象模型JOM
JVM内存结构 Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途.其中有些区域随着虚拟机进程的启动而存在,而有些区 ...
- 小菜鸟之java内存结构
JVM启动流程: JVM基本结构图: <深入理解Java虚拟机(第二版)>中的描述是下面这个样子的: Java中的内存分配: Java程序在运行时,需要在内存中的分配空间.为了提高运算效率 ...
- JVM高级特性-一、java内存结构区域介绍
区域划分: java虚拟机在执行程序的过程中,将内存分为功能不同的几个区域,如下图: 此图列出了内存划分的各个区域,其中 线程私有的:程序计数器.虚拟机栈.本地方法栈 线程共享的:堆.方法区 下面,逐 ...
- 深入理解java虚拟机【Java内存结构】
Java虚拟机规范规定的java虚拟机内存其实就是java虚拟机运行时数据区,其架构如下: 其中方法区和堆是由所有线程共享的数据区. Java虚拟机栈,本地方法栈和程序计数器是线程隔离的数据区. (1 ...
随机推荐
- windows7下系统保护中出现错误“文件名、目录名或卷标语法不正确。(0x8007007B)“ 以及保护设置列表中出现“Windows7_os(c:)(找不到)”选项时的解决方法
windows7下系统保护功能很是鸡肋,有事会出现一下两个问题: 1.出现错误“文件名.目录名或卷标语法不正确.(0x8007007B) 2.保护设置列表中出现“Windows7_os(c:)(找不到 ...
- (转)使用 Advanced Installer 打包 一键安装Web应用程序
使用 Advanced Installer 打包 一键安装Web应用程序 安装预览: 资源下载: 示例安装包 操作流程: 1.新建Asp.net Application. 2.设置 ...
- template_11实参演绎
1,演绎过程匹配类型A(来自实参的类型),参数化类型P(行参参数声明)如果被声明的参数是一个引用声明g(T& )那么P就是所引用类型T:f(T)中P就是所声明的参数类: decay指从数组和函 ...
- IIS安装Web Deploy之后没有显示右键菜单
Bug描述: 使用IIS自带的"Web平台安装程序"安装完Web Deploy组件之后,鼠标右键点击网站,弹出的菜单中并没有新增的"部署"选项. Bug解决: ...
- Android 源码编译及常见错误及解决方法
最近要往arm开发板上移植android系统,大大小小的问题遇到了太多太多,都是泪啊.本人初接触嵌入式开发,对问题的根源不是太了解,不过好在每解决一个问题,便记录一下.话不多说,正式罗列问题: hos ...
- 引用、return
C语言中没有引用,引用(reference)是c++对c语言的重要扩充.通俗点说,引用就是“起别名”.比如变量data,和它的引用 RefData.虽然名字不同,但是操作他们的时候,都操作的是相同的内 ...
- 关于C#正则表达式MatchCollection类的总结,正则表达式的应用
认识MatchCollection 类 表示通过以迭代方式将正则表达式模式应用于输入字符串所找到的成功匹配的集合. 命名空间: System.Text.RegularExpressions 属性:C ...
- T-SQL数组循环
T-SQL对字符串的处理能力比较弱,比如要循环遍历象1,2,3,4,5这样的字符串,如果用数组的话,遍历很简单,但是T-SQL不支持数组,所以处理下来比较麻烦.下边的函数,实现了象数组一样去处理字符串 ...
- 基础学习总结(三)--文本、SD卡数据读写
简单的文本数据写入文件不需要权限,读写SD卡在4.0版本前需要写权限.在4.0后需要读写权限 布局: <LinearLayout xmlns:android="http://schem ...
- 利器: 用Siege做Web服务器压测
用「Web压测」关键词检索,能找到好多进行压测的工具,比如ab.Http_load.Webbench.Siege这些,不过今天并不是要对这些工具做对比,毕竟我们只是想得到一个结果.本文主要介绍Sieg ...