1 jvm内存模型
jvm规范将jvm用到的内存分成如下几个不同的功能区:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 方法区(运行时常量池)
- 堆
当然,这只是逻辑上的划分,不同的虚拟机在实现时可能会略有不同,例如有些虚拟机将虚拟机栈和本地方法栈合在了一起(Hotspot就是)。因为这些区域在java程序运行时的作用不同,因此每个区块可能发生的问题也不同,下表描述了这些区域的作用和可能发生的问题:
区域
|
作用
|
作用域
|
可能的问题
|
HotSpot虚拟机中控制其大小的参数 |
程序计数器 |
线程当前所执行的字节码的行号指示器 |
线程私有 |
无 |
|
虚拟机栈 |
存储java方法执行时的局部变量表、操作栈
、动态链接和方法出口等信息
其中局部变量表存放了编译器可知的各种基本数据类型、对象引用,它的大小在编译器确定
|
线程私有 |
栈深度大于虚拟机允许的最大深度时候,抛出StackOverflowError
动态扩展时无法申请足够的内存时将抛出OutOfMemoryError
|
-Xss10M |
本地方法栈 |
其作用与虚拟栈类似,只是它是在虚拟机执行Native方法时起作用 |
线程私有 |
StackOverflowError和OutOfMemoryError |
-Xss10M HotSpot虚拟机两个栈合二为一
|
堆 |
存放数组和对象实例,这里是垃圾搜集器管理的主要区域。可以分为新生代和老生代;在使用复制垃圾回收策略时,新生代又可以分为Eden空间和Survivor空间。堆在物理上可以是不连续的,逻辑上连续即可 |
线程共享 |
OutOfMemoryError |
-Xms10M
-Xmx20M
-Xmn10M,新生代用10M
|
方法区 |
存放被虚拟机加载的类信息、常量
、静态变量和jti编译器编译的代码等;
jvm规范上,方法区是堆的一个逻辑区域,并且Hotspot虚拟机还把它称为永久代(与新生代、老生代对应
)。 |
线程共享 |
OutOfMemoryError |
-XX:PermSize=10M
-XX:MaxPermSize=20M
|
运行时常量池 |
方法区的一部分,存放编译期生成的各种字面量和符号引用,以及运行时生成的新的常量; |
线程共享 |
OutOfMemoryError |
-XX:PermSize=10M
-XX:MaxPermSize=20M |
除了上述虚拟机运行时的数据区外,jkd1.4后包含的NIO还会用到虚拟机外的内存,在块区域叫做直接内存(本机直接内存),具体的实现原理可以参考NIO。虽然,它不在虚拟机内存之列,但是因为受到本机总内存的限制,所以也有可能产生OutOfMemoryError.
2
内存分配策略
虚拟机的堆内存分配策略根据使用的垃圾收集器的不同(下文再讲)可能会有不同,但是如下几条原则应该说是几条最基本的原则:
- 对象优先在Eden区分配:当Eden不够在为新的对象分配时,会触发一次Minor GC,如果GC之后还是Eden还是没有足够的空间,则可能直接将对象分配到老生代去;
- 大对象直接进入老年代:避免因为需要进行Minor GC而在Eden区和Survivor区中复制;对于超大的对象往往Minor GC之后Eden区还是没有足够的空间或者说在Minor GC时,survivor区空间小于大对象,这时大对象还是会被移到老年代,所以对于大对象可以直接进入老年代;-XX:PretenureSizeThreshold参数设置大对象的阀值,大于这个值的对象直接进入老年代;
- 长期存活的对象进入老年代:对象在Eden区出生,经过一次Minor GC后仍然存活并且能被Survivor容纳(如果不能被Survivor容纳则会被直接移到老年代),它的年龄就增加1岁,当它的年龄增加到一定大小时(默认为15岁),则会被移到老年代中。可以用-XX:MaxTenuringThreshod=n参数设置这个年龄的阀值。
- 动态对象年龄判断:如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,则年龄大于等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshod要求的年龄;
- 空间分配担保:每次进行Minor GC之前都会先进行检测之前每次Minor GC晋升到老年代的平均大小是否大于当前老年代的剩余空间大小,如果是,则直接改为进行一次Full GC;否则,则看HandlePromotionFailure设置是否允许空间分配担保失败,如果允许失败,那么只进行Minor GC,否则还是要进行一次Full GC;
3
垃圾回收算法与垃圾回收器
相比内存分配,垃圾回收是一个更加复杂的问题,处理不当也是更危险的问题。研究垃圾回收问题往往需要研究以下几个问题:
3.1
垃圾回收处理的区域
第一节在介绍jvm对内存区域的划分时,就介绍了每个区域的作用域和可能发生的问题。除了程序计数器之外,其他的区域都有可能发生OutOfMemoryError(Oom),那是不是所有会发生Oom问题的区域都需要进行垃圾回收呢?答案是否定的,对于虚拟机栈和本地方法栈因为下面的两个原因其实不需要额外的去进行复杂的垃圾回收的:
- 其内存使用都是线程级别的,随着线程的结束所使用的内存也自然的会被回收;
- 栈中的内存基本都是在编译期就已知的,现代jvm在栈中发生Oom的可能性不大;
而剩下的堆和方法区(包括运行时常量池)才是垃圾回收的重点区域,另外因为方法区存在的主要是类信息和运行时常量,通常这两部分可回收的垃圾较少,并且想要回收类信息的条件还非常的苛刻。所以说堆内存的回收才是垃圾回收关注的重点的重点,当然堆中垃圾回收的机制、算法也都适用方法区(jvm规范上,方法区本身就是堆的一个逻辑部分)。
3.2 垃圾回收的内容
因为堆是垃圾回收的重点区域,所以主要已堆上存储的对象实例作为垃圾回收讨论的对象。那么,垃圾回收到底回收些什么呢?答案就是堆中已“死”的对象。如何判断对象已“死”呢?主要有两种方法:
- 引用计数法:对象有N个地方引用了,其计数就是N,如果计数为0,则说明是可回收的对象;引用计数法实现简单,效率也高,但它无法解决循环引用的问题;
- 根搜索算法:已"GC Root"对象为起点,向下建立引用链,不在任何引用链中的对象即是可回收的对象。根搜索算法能解决循环依赖问题。
任何可以被回收的对象在真正被回收之前,都会调用其finalize()方法,如果对象在finalize()中又将自己和外部的引用进行了关联,则该对象又称为不可回收的对象了。但是一个对象的finalize()方法只会执行一次,下次再被标记为可回收时,将不再执行,所以就会被回收了。所有对象的finalize()方法都是在一个叫F-QUEUE的队列中执行的。finalize()方法一定会被触发,但不保证会被执行完,这是为了防止某些对象的finalize()方法执行出问题时,会拖垮整个F-QUEUE。
3.3 垃圾回收算法
知道了哪些对象是可回收的垃圾,下一步就是对这些对象进行回收。目前,回收的算法主要有以下几种:
- 标记--清除算法:先按3.2节介绍的方法标记出需要回收的对象,然后统一清除掉可以回收的对象的内存;这种方法效率不高,并且因为只是简单的将垃圾对象占用的内存清除,因此会产生很多不连续的内存碎片,这样在需要为大对象分配内存时就会出现堆的总内存还剩很多,但是没有连续的内存分给这个大对象而导致又一次垃圾回收甚至是是导致Omm的问题。
- 复制算法:将可用内存划分为一块较大的Eden块和两块相同大小的较小的survivor块,每次适用时只使用Eden块和一块survivor块,但进行垃圾回收时,先将Eden块和使用的survivor块中有用的对象复制到没有使用的survivor块中,再整体清空Eden块和先前使用的survivor块,接下去的运行将使用Eden块和第二块survivor块。当空闲的survivor块不够存放所有的有用的对象时,就需要依赖其他内存(老生代)进行内存分配担保,对象直接进入老生代。
- 标记--整理算法:标记阶段与标记--清除算法相同,只是清理阶段不是简单的清除可回收的对象的内存区域,而是将所有有用的对象都移到内存区的前面来,形成连续的内存空间,而整体清理剩下的内存空间。
3.4 垃圾收集器
垃圾收集器只是垃圾搜索算法的一个实现,只是不同的收集器可能在追求的目标上和实现方式上有所不同而已。不同的JVM带有的垃圾收集器不尽相同,1.6 Update 22版的HotSpot虚拟机带有的垃圾收集器如下:
名称 |
使用的算法 |
作用的区域 |
实现方式 |
追求的目标 |
可共用的收集器 |
Serial |
复制算法 |
新生代 |
单线程 |
用户响应优先 |
CMS |
ParNew |
复制算法 |
新生代 |
多线程 |
用户响应优先 |
CMS,Serial Old |
Parallel Scavenge |
复制算法 |
新生代 |
多线程 |
吞吐量优先 |
Serial Old,Parallel Old |
CMS |
标记-清除算法 |
老生代 |
多线程 |
用户响应优先 |
Serial,ParNew |
Serial Old |
标记-整理算法 |
老生代 |
单线程 |
用户响应优先 |
Serial,ParNew,Parallel Scavenge |
Parallel Old |
标记-整理算法 |
老生代 |
多线程 |
用户响应优先 |
Parallel Scavenge |
一般虚拟机会在启动时,为新生代和老生代设置不同的默认的垃圾收集器,我们可以根据硬件的情况
、系统的实现特性和
追求的目标在不同的收集器组合中选择最合适的组合。
4 总结
关于jvm内存分配和回收,看起来更多的是理论上内容,没有一些实际的技术。为什么要去学习和研究呢?应该有下面3个原因:
- 为系统开发做指导:知道了jvm会如何处理对象,就可以知道什么杨的代码编写方式可能会导致性能问题:例如批量产生大量长时间存在内存的大对象可能就会频繁触发Full GC而导致性能问题;知道了这些之后,就可以为解决问题设计更好的方案,而非仅仅是解决问题而已;
- 优化系统性能,特别是当垃圾回收成为系统达到更高并发量的瓶颈时:优化可以考虑从以下几个方面入手:
- 减少JTI编译时间和类加载时间
- 调整内存设置控制垃圾收集频率
- 选择合适的垃圾收集器
- 排查内存溢出和内存泄漏问题:排查的主要思路和系统优化其实类似。
分享到:
相关推荐
JVM内存管理是Java平台的一个重要特性,其内存空间的分配和回收机制对Java应用程序的性能和稳定性有着至关重要的影响。 首先,JVM内存管理涉及的内存空间主要分为方法区(Method Area)、堆(Heap)、本地方法栈...
本资料《JVM基础-超清文字版.pdf》将深入探讨JVM的基础知识,包括其架构、内存模型、类加载机制以及性能优化等方面。 1. **JVM架构** - 类装载器:负责加载类文件到JVM中,分为引导类加载器、扩展类加载器和应用类...
以上只是JVM众多知识中的一部分,实际上,JVM涉及的领域还包括内存模型、线程管理、异常处理、类加载策略等。理解JVM的工作原理对于编写高效、稳定的Java程序至关重要。通过研究这个压缩包中的资源,你可以更深入地...
JVM原理-jvm内存及相关图示 JVM(Java Virtual Machine)是Java开发工具包(JDK)的一部分,它的主要作用是将Java字节码文件(.class文件)解释并执行,使得Java语言可以跨平台运行。JVM主要由类加载子系统、执行...
JVM内存调优是一个复杂的过程,需要根据应用程序的特性和资源需求进行调整。通常,我们需要关注垃圾收集器的性能,如新生代与老年代的占比,以及内存分配和回收的效率。同时,监控系统的CPU使用率、内存占用和GC日志...
JVM 的运行机制 多线程 JVM 的内存区域 JVM 会创建操作系统的接口创建一个原生线程。JVM 线程和操作系统线程是一一对应的
JVM内存管理是Java虚拟机的核心机制之一,其主要包含对象的创建、内存分配、垃圾回收以及内存释放等过程。在JVM中,垃圾回收(GC)是自动管理内存的关键技术,其目的是回收不再使用的对象所占用的内存空间,以避免...
Java虚拟机(JVM)是Java程序运行的核心,它是Java平台的一个重要组成部分。"JVM详解-淘宝内部资料"提供了一套深入理解JVM的资源,涵盖了从基础到高级的各种主题,包括Java虚拟机的生命周期、JVM的体系结构、各个...
《深入浅出:Rust语言实现Mini-JVM》 在当今的编程世界中,Java虚拟机(JVM)因其高效、跨平台的特性而被广泛使用。然而,JVM的内部工作原理对于许多开发者来说仍然是一个神秘的概念。本文将探讨一个特殊的项目——...
JVM内存模型是理解Java性能优化的关键。按照JVM规范,内存主要分为五个区域:程序计数器、Java虚拟机栈、本地方法栈、堆和方法区(在Java 8之后被元空间取代)。每个区域都有其特定的用途和生命周期管理: 1. **...
自己在公司做分享时做的培训文档,包括了jvm的内存结构以及每个内存结构的一些说明,仅供公司内部培训用,不做其他商业用途。
-Xmx 设置 JVM 的最大可用内存,-Xms 设置 JVM 的初始内存大小。二者的值可以相同,以避免每次垃圾回收完成后 JVM 重新分配内存。 -Xmn 设置年轻代的大小,整个 JVM 内存大小=年轻代大小 + 年老代大小 + 持久代...
一、JVM内存结构 1. **程序计数器(Program Counter Register)**:每个线程都有一个独立的程序计数器,用于存储当前线程所执行的字节码指令地址。 2. **虚拟机栈(Java Stack)**:存储方法的局部变量、操作数栈...
在这份由Sun Microsystems公司出版的《JVM内存管理白皮书》中,我们可以找到关于Java虚拟机(JVM)内存管理的详细介绍和深入分析。这份文档对于想要深入了解JVM工作原理的读者来说是一份宝贵的学习资料。在这份...
总之,MAT作为一款强大的JVM内存分析工具,对于优化Java应用的内存使用,提升应用性能,尤其是对于Mac OS X平台的开发者来说,是不可或缺的利器。通过熟练掌握MAT的使用,开发者可以更有效地管理和优化应用程序的...
AOP(Aspect-Oriented Programming)是一种编程范式,它将关注点分离,使得系统中的横切关注点(如日志、事务管理)可以独立于业务逻辑进行编写和维护。JVM-SANDBOX通过动态代理技术实现了这一目标,允许开发者在...
本资源详细讨论了 JVM 内存参数的配置和调优,包括 JVM 的结构、内存管理、垃圾回收、堆和非堆内存、内存分配和限制等方面,为开发人员和运维人员提供了一份详细的指南,以帮助他们更好地理解和优化 JVM 的性能。
JVM调优是一项核心技能,可以帮助我们优化应用程序的性能,减少内存消耗,提高响应速度,以及避免可能出现的垃圾收集问题。"练习JVM调优-jvm_demo.zip"是一个压缩包,包含了用于JVM调优实践的示例项目"jvm_demo-...
1. **理解JVM内存结构**:Java内存主要分为堆内存(Heap)和非堆内存(Non-Heap),其中堆内存又分为新生代(Young Generation)、老年代(Tenured Generation或Old Generation)和持久代(Permanent Generation或...