Java内存架构(Java内存模型)
上面是堆的Java内存模型以及Java虚拟机(JVM)中运行的任何Java应用程序的PermGen。 还提供了比率,以使您更好地了解如何在每种世代类型之间分配允许的内存。 以上所有内容完全适用于Java 1.7版(含)。 上面也称为内存模型的“管理区域”。
除上述内容外,还有一个堆栈区域,可以使用-Xss选项进行配置。 该区域保存堆上的引用,本机引用,pc寄存器,代码缓存和所有线程的局部变量。 这也称为内存模型的“本地区域”。
Java内存模型的受管区域(Java内存体系结构)
[年轻一代/苗圃]伊甸园空间
所有新对象都首先在Eden Space中创建。 一旦达到由JVM确定的任意阈值,就会启动次要垃圾回收(Minor GC)。它首先删除所有非引用对象,并将引用对象从“ eden”和“ from”移到“ to”幸存者空间。 GC结束后,将交换“从”和“到”角色(名称)。
[年轻一代/苗圃]幸存者1(来自)
这是幸存者空间的一部分(您可能认为这是幸存者空间中的角色 )。 这是上一个垃圾回收(GC)期间的“ to”角色。
[年轻一代/苗圃] Suvrivor 2(至)
这也是幸存者空间的一部分(您可能认为这也是幸存者空间中的角色 )。 在这里,在GC期间,所有引用的对象
从'from'和'eden'移到。
[上一代]终身任职
根据阈值限制,可以使用-XX:+ PrintTenuringDistribution来检查阈值限制,该限制按年龄显示对象(以字节为单位的空间)–对象从“到” 幸存者空间移动到Tenured空间。 “年龄”是指它在幸存者空间内移动的次数。 还有其他重要的标志,例如-XX:InitialTenuringThreshold,-XX:MaxTenuringThreshold和-XX:TargetSurvivorRatio ,它们可以优化使用权和幸存者空间。 通过设置-XX:InitialTenuringThreshold和-XX:MaxTenuringThreshold,我们允许'Age'的初始值和最大值,同时保持-XX:+ NeverTenure和-XX指定的'Survivor(To)'中的利用率百分比。 + AlwaysTenure,正如他们建议的那样,要么永不保管对象(使用风险较大 ),相反的用法是始终保有权,即始终使用“老一代”。 这里发生的垃圾收集是主要垃圾收集(主要GC)。 通常在堆已满或旧代已满时触发。 这通常是接管执行垃圾回收的“ 世界停止 ”事件或线程。 还有另一种称为完全垃圾收集(Full GC)的GC,它涉及其他内存区域,例如permgen空间。
与整个堆相关的其他重要且有趣的标志是-XX:SurvivorRatio和-XX:NewRatio ,它们指定eden空间与幸存者空间的比率以及旧一代与新一代的比率。
[永久世代] Permgen空间
“ Permgen”用于存储以下信息:常量池(内存池),字段和方法数据以及代码。 它们每个都与名称所暗示的特征相同。
垃圾收集算法
串行GC(-XX:UseSerialGC):年轻一代和老一代的GC
为年轻和终身使用的一代使用简单的标记扫描紧凑循环。 这对于客户端系统以及内存占用量少和cpu较小的系统来说非常有用
并行GC(-XX:UseParallelGC):年轻一代和老一代的GC
这使用了N个线程,可以使用-XX:ParallelGCThreads = N进行配置,这里N也是CPU内核的数量。 用于垃圾收集。 它在Young代中将这N个线程用于GC,而在Old代中仅使用一个线程。
并行旧GC(-XX:UseParallelOldGC):年轻一代和老一代的GC
这与Parallel GC相同,不同之处在于它在旧一代和年轻一代中均使用N个线程进行GC。
并发标记和扫描GC(-XX:ConcMarkSweepGC):旧Generaton上的GC
顾名思义,CMS GC将GC所需的停顿最小化。 创建高响应性的应用程序最有用,并且仅在旧一代中才执行GC。 它为GC创建了多个线程,这些线程与应用程序线程并发工作,可以使用-XX:ParallelCMSThreads = n指定这些线程。
G1 GC(-XX:UseG1GC):年轻一代和老年人一代的GC(通过将堆分成相等大小的区域)
这是一个并行,并发且递增压缩的低暂停垃圾收集器。 它是在Java 7中引入的,其最终目标是取代CMS GC。 它将堆划分为多个大小相等的区域,然后执行GC,通常从实时数据较少的区域开始-因此,即“垃圾优先”。
最常见的内存不足问题
所有Java开发人员都应该知道的最常见的内存不足问题,以便正确地开始调试,如下所示:
- 线程“ main”中的异常:java.lang.OutOfMemoryError:Java堆空间这并不一定意味着内存泄漏,这可能是由于为堆配置的空间较小所致。 否则,在寿命长的应用程序中,可能是由于无意中提到了对堆对象的引用(内存泄漏)。 甚至应用程序调用的API都可能包含对不必要的对象的引用。 同样,在过度使用终结器的应用程序中,有时对象会排队到终结队列中。 当这样的应用程序创建更高优先级的线程并导致finalizaton队列中的对象越来越多时,它可能导致内存不足。
- 线程“ main”中的异常:java.lang.OutOfMemoryError:PermGen空间如果加载了许多类和方法,或者创建了很多字符串文字,尤其是通过使用intern()(从JDK 7开始,不再使用实习字符串) (PermGen的一部分)–则发生这种类型的错误。 发生此类错误时,文本ClassLoader.defineClass可能会出现在所打印的堆栈跟踪顶部附近。
- 线程“ main”中的异常:java.lang.OutOfMemoryError:请求的数组大小超出VM限制当请求的数组大小大于可用堆大小时,再次发生这种情况。 如果为数组大小请求一个非常大的值,通常可能由于运行时的程序错误而发生。
- 线程“ main”中的异常:java.lang.OutOfMemoryError:请求<r>的<s>个字节。 交换空间不足?
通常这可能是内存泄漏的根本原因。 当操作系统没有足够的交换空间或另一个进程占用系统上所有可用的内存资源时,就会发生这种情况。 简而言之,由于空间耗尽,它无法从堆中提供请求空间。 该消息指示失败的请求的大小“ s”(以字节为单位)以及内存请求的原因“ r”。 在大多数情况下,消息的<r>部分是报告分配失败的源模块的名称,尽管在某些情况下它表示原因。 - 线程“ main”中的异常:java.lang.OutOfMemoryError:<原因> <堆栈跟踪>(本机方法)
这表明本机方法遇到分配失败。 根本原因是该错误发生在JNI中,而不是在JVM内部执行的代码中发生。 当本机代码不检查内存分配错误时,应用程序将崩溃而不是耗尽内存。
内存泄漏的定义
“将内存泄漏视为一种疾病,而将OutOfMemoryError视为一种症状。 但是,并非所有OutOfMemoryErrors都暗示内存泄漏,并且并非所有内存泄漏都将自身表现为OutOfMemoryErrors。 ”
在《计算机科学》中,内存泄漏是一种资源泄漏,当计算机程序错误地管理内存分配以致不再释放不再需要的内存时,就会发生这种情况。 在面向对象的编程中 ,当对象存储在内存中但无法被运行的代码访问时,可能会发生内存泄漏。
Java中内存泄漏的常见定义:
当不必要的对象引用被不必要地维护时,就会发生内存泄漏。
在Java中,内存泄漏是指某些对象不再被应用程序使用,但是GC无法将其识别为未使用的情况。
当程序中不再使用某个对象,但仍在无法访问的某个位置引用该对象时,将出现内存泄漏。 因此,垃圾收集器无法删除它。 用于此对象的内存空间不会释放,并且用于程序的总内存将增加。 随着时间的推移,这将降低性能,并且JVM可能会耗尽内存。
从某种意义上说,当在永久性空间上无法分配任何内存时,就会发生内存泄漏。
内存泄漏的一些最常见原因是:
- 线程局部变量
- 圆形和复杂双向参考
- JNI内存泄漏
- 可变的静态字段(最常见)
我建议使用与JDK捆绑在一起的Visual VM,以开始调试内存泄漏问题。
内存泄漏的常见调试
- NetBeans探查器
- 使用jhat实用程序
- 创建堆转储
- 在运行过程中获取堆直方图
- 在OutOfMemoryError处获取堆直方图
- 监视即将完成的对象数量
- 第三方内存调试器
调试内存泄漏问题的常用策略或步骤包括:
- 识别症状
- 启用详细垃圾回收
- 启用分析
- 分析痕迹
祝幸福时光,解决Java内存问题!