工作中常见的OOM?你了解JVM调优吗?
<h2 id="工作中常见的6种oom问题">工作中常见的6种OOM问题</h2><h3 id="堆内存oom">堆内存OOM</h3>
<p>堆内存OOM是最常见的OOM了。</p>
<p>出现堆内存OOM问题的异常信息如下:</p>
<pre><code class="language-java">java.lang.OutOfMemoryError: Java heap space
</code></pre>
<p>此OOM是由于JVM中heap的最大值,已经不能满足需求了。</p>
<p>举个例子:</p>
<pre><code class="language-java">@Test public void test01() {
List list = Lists.newArrayList();
while (true) {
list.add(new OOMTests());
}
}
</code></pre>
<p>这里创建了一个list集合,在一个死循环中不停往里面添加对象。</p>
<p>执行结果:</p>
<p><img src="https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202411251651728.png" alt="" loading="lazy"></p>
<p>出现了java.lang.OutOfMemoryError: Java heap space的堆内存溢出。</p>
<p>很多时候,excel一次导出大量的数据,获取在程序中一次性查询的数据太多,都可能会出现这种OOM问题。</p>
<p>我们在日常工作中一定要避免这种情况。</p>
<h3 id="栈内存oom">栈内存OOM</h3>
<p>有时候,我们的业务系统创建了太多的线程,可能会导致栈内存OOM。</p>
<p>出现堆内存OOM问题的异常信息如下:</p>
<pre><code class="language-java">java.lang.OutOfMemoryError: unable to create new native thread
</code></pre>
<p>举个例子</p>
<pre><code class="language-java">public class StackOOMTest {
public static void main(String[] args) {
while (true) {
new Thread().start();
}
}
}
</code></pre>
<p>使用一个死循环不停创建线程,导致系统产生了大量的线程。</p>
<p>如果实际工作中,出现这个问题,一般是由于创建的线程太多,或者设置的单个线程占用内存空间太大导致的。</p>
<blockquote>
<p>建议在日常工作中,多用线程池,少自己创建线程,防止出现这个OOM。</p>
</blockquote>
<h3 id="栈内存溢出">栈内存溢出</h3>
<p>我们在业务代码中可能会经常写一些 递归调用,如果递归的深度超过了JVM允许的最大深度,可能会出现栈内存溢出问 题。</p>
<p>出现栈内存溢出问题的异常信息如下:</p>
<pre><code class="language-java">java.lang.StackOverflowError
</code></pre>
<p>举个例子</p>
<pre><code class="language-java">@Test
public void test03() {
recursiveMethod();
}
public static void recursiveMethod() {
// 递归调用自身
recursiveMethod();
}
</code></pre>
<p><img src="https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202411251703408.png" alt="" loading="lazy"></p>
<p>出现了java.lang.StackOverflowError栈溢出的错误。</p>
<p>我们在写递归代码时,一定要考虑递归深度。即使是使用 parentId一层层往上找的逻辑,也最好加一个参数控制递归 深度。防止因为数据问题导致无限递归的情况,比如:id和 parentId的值相等。</p>
<h3 id="直接内存oom">直接内存OOM</h3>
<p>直接内存不是虚拟机运行时数据区的一部分,也不是《Java 虚拟机规范》中定义的内存区域。</p>
<p>它来源于 NIO ,通过存在堆中的 DirectByteBuffer 操作 Native内存,是属于堆外内存 ,可以直接向系统申请的内存空间。 出现直接内存OOM问题时异常信息如下:</p>
<pre><code class="language-java">java.lang.OutOfMemoryError: Direct buffer memory
</code></pre>
<p>例如:</p>
<pre><code class="language-java">private static final int BUFFER = 1024 * 1024 * 20;
@Test
public void test04() {
ArrayList<ByteBuffer> list = new ArrayList<>();
int count = 0;
try {
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
list.add(byteBuffer);
count++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
System.out.println(count);
}
}
</code></pre>
<p>会看到报出来java.lang.OutOfMemoryError: Direct buffer memory直接内存空间不足的异常。</p>
<h3 id="gc-oom">GC OOM</h3>
<p>GC OOM 是由于JVM在GC时,对象过多,导致内存溢出,建 议调整GC的策略。 出现GC OOM问题时异常信息如下:</p>
<pre><code class="language-java">java.lang.OutOfMemoryError: GC overhead limit exceeded
</code></pre>
<p>为了方便测试,我先将idea中的最大和最小堆大小都设置成 10M,例如下面这个例子:</p>
<pre><code class="language-java">public class GCOverheadOOM {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
});
}
}
}
</code></pre>
<p>出现这个问题是由于JVM在GC的时候,对象太多,就会报这 个错误。 我们需要改变GC的策略。 在老代80%时就是开始GC,并且将-XX:SurvivorRatio(- XX:SurvivorRatio=8)和-XX:NewRatio(- XX:NewRatio=4)设置的更合理。</p>
<h3 id="元空间oom">元空间OOM</h3>
<p>JDK8 之后使用 Metaspace 来代替 永久代 ,Metaspace是方 法区在 HotSpot 中的实现。</p>
<p>Metaspace不在虚拟机内存中,而是使用本地内存也就是在 JDK8中的 ClassMetadata ,被存储在叫做Metaspace的 native memory。</p>
<p>出现元空间OOM问题时异常信息如下:</p>
<pre><code class="language-java">java.lang.OutOfMemoryError: Metaspace
</code></pre>
<p>为了方便测试,我们修改一下idea中的JVM参数,增加下面的配 置:</p>
<pre><code class="language-java">-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
</code></pre>
<p>指定了元空间和最大元空间都是10M。 接下来,看看下面这个例子:</p>
<pre><code class="language-java">public class MetaspaceOOMTest {
static class OOM {
}
public static void main(String[] args) {
int i = 0;
try {
while (true) {
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOM.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[]
return methodProxy.invokeSuper(o, args);
}
});
enhancer.create();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
</code></pre>
<p>程序最后会报java.lang.OutOfMemoryError: Metaspace的 元空间OOM。 这个问题一般是由于加载到内存中的类太多,或者类的体积太 大导致的。</p>
<h2 id="oom一定会导致jvm退出吗">OOM一定会导致JVM退出吗</h2>
<p>在Java中,发生了OutOfMemoryError(OOM)不一定会导致整个JVM退出。是否退出取决于发生OOM错误的线程和错误处理逻辑。这是一个复杂的问题,具体行为会因应用程序实现方式、错误发生的情境以及错误处理策略而异。</p>
<ul>
<li>主线程中未处理的OOM:如果在主线程中发生OOM且没有被捕获,JVM通常会终止程序并退出。这是因为JVM中没有其他存活的非守护线程来保持程序运行。</li>
<li>子线程中未处理的OOM:在非主线程中,如果OOM发生且未被捕获,该线程会停止执行。但如果其他非守护线程仍在运行,JVM不会退出。</li>
<li>捕获并处理OOM:如果在代码中捕获并正确处理了OOM错误,JVM则可以继续执行其余的程序代码。合适的错误处理可能包括释放内存资源或提示用户进行适当的操作。</li>
</ul>
<p>注意:</p>
<ul>
<li>不建议频繁捕获OOM并继续执行程序,因为这样可能表明程序有严重的内存管理问题,应尽量优化内存使用。</li>
<li>在关键路径中发生OOM时,通常应记录日志并考虑安全停机,因为无法保证系统在内存压力下的正确性。</li>
</ul>
<h2 id="垃圾回收调优的主要目标是什么">垃圾回收调优的主要目标是什么?</h2>
<p>分别是最短暂停时间和高吞吐量</p>
<ol>
<li>最短暂停时间:垃圾回收调优的首要目标是减少应用程序的停顿时间,确保在垃圾回收过程中尽量保持应用的响应能力,特别是对于实时或高并发应用</li>
<li>高吞吐量:第二个目标是提高应用的吞吐量,即在单位时间内完成更多的业务处理,通过合理的GC策略和配置,减少GC的频率和时间,从而提升整体性</li>
</ol>
<p>针对最短暂停时间和高吞吐举个例子:</p>
<ul>
<li>方案一:每次 GC 停顿 100 ms,每秒停顿 5 次。</li>
<li>方案二:每次 GC 停顿 200 ms,每秒停顿 2次。</li>
</ul>
<p>两个方案相对而言第一个时延低,第二个吞吐高,基本上两者不可兼得。所以调优时候需要明确应用的目标。</p>
<h2 id="如何对-java-的垃圾回收进行调优">如何对 Java 的垃圾回收进行调优?</h2>
<blockquote>
<p>GC调优这种问题肯定是具体场景具体分析,但是在面试中就不要讲太细,大方向说清楚就行,不需要涉及具体的垃圾收集器比如 CMS 调什么参数,G1 调什么参数之类的。</p>
</blockquote>
<p>GC 调优的核心思路就是尽可能的使对象在年轻代被回收,减少对象进入老年代。</p>
<p>具体调优还是得看场景根据 GC日志具体分析,常见的需要关注的指标是 Young GC 和 Ful GC 触发频率、原因、晋升的速率、老年代内存占用量等等。比加发现频繁产生FullGC,分析日志之后发现没有内存泄漏,只是 Young GC之后会有大量的对象进入老年代,然后最终触发FullGC,所以就能得知是 Survivor 空间设置太小,导致对象过早进入老年代,因此调大Survivor。或者是晋升年龄设置的太小,也有可能分析日志之后发现是内存泄漏、或者有第三方类库调用了 System.gc 等等。反正具体场景具体分析,核心思想就是尽量在新生代把对象给回收了,</p>
<p>基本上这样说就行了,然后就等着面试官延伸了</p>
<h2 id="常用的jvm配置参数有哪些">常用的JVM配置参数有哪些?</h2>
<p>记住前两个,其它的使用时再查就行</p>
<p>JVM(Java虚拟机)的启动参数用于配置和调整Java应用程序的运行时行为。以下是一些常用的JVM启动参数:</p>
<ul>
<li>-Xmx:指定Java堆内存的最大限制。例如,-Xmx512m 表示最大堆内存为512兆字节。</li>
<li>-Xms:指定Java堆内存的初始大小。例如,-Xms256m 表示初始堆内存为256兆字节。</li>
<li>-Xss:指定每个线程的堆栈大小。例如,-Xss256k 表示每个线程的堆栈大小为256千字节。</li>
<li>-XX:MaxPermSize(对于Java 7及之前的版本)或 -XX:MaxMetaspaceSize(对于Java 8及以后的版本):指定永久代(Java 7及之前)或元空间(Java 8及以后)的最大大小。</li>
<li>-XX:PermSize(对于Java 7及之前的版本)或 -XX:MetaspaceSize(对于Java 8及以后的版本):指定永久代(Java 7及之前)或元空间(Java 8及以后)的初始大小。</li>
<li>-Xmn:指定年轻代的大小。例如,-Xmn256m 表示年轻代大小为256兆字节。</li>
<li>-XX:SurvivorRatio:指定年轻代中Eden区与Survivor区的大小比例。例如,-XX:SurvivorRatio=8 表示Eden区与每个Survivor区的大小比例为8:1。</li>
<li>-XX:NewRatio:指定年轻代与老年代的大小比例。例如,-XX:NewRatio=2 表示年轻代和老年代的比例为1:2。</li>
<li>-XX:MaxGCPauseMillis:设置垃圾回收的最大暂停时间目标。例如,-XX:MaxGCPauseMillis=100 表示垃圾回收的最大暂停时间目标为100毫秒。</li>
<li>-XX:ParallelGCThreads:指定并行垃圾回收线程的数量。例如,-XX:ParallelGCThreads=4 表示使用4个线程进行并行垃圾回收。</li>
<li>-XX:+UseConcMarkSweepGC:启用并发标记清除垃圾回收器。</li>
<li>-XX:+UseG1GC:启用G1(Garbage First)垃圾回收器。</li>
<li>-Dproperty=value:设置Java系统属性,可以在应用程序中使用 System.getProperty("property") 来获取这些属性的值。</li>
</ul>
<p>这些是一些常见的JVM启动参数,可以根据应用程序的需求和性能调优的目标进行调整。JVM启动参数的使用可以显著影响应用程序的性能和行为,因此在设置这些参数时需要谨慎。同时,JVM支持的启动参数因不同的JVM版本和供应商而有所不同,建议查阅相关文档以获取更详细的信息。</p>
<h2 id="你常用哪些工具来分析-jvm-性能">你常用哪些工具来分析 JVM 性能?</h2>
<ul>
<li>jmap:用于生成堆转储的命令行工具,可以用于分析JVM内存使用情况,尤其是内存泄漏问题</li>
<li>jstack:用于生成线程转储的命令行工具,可以用于分析线程状态,排查死锁等问题</li>
<li>jistat:用于监控JVM统计信息的命令行工具,提供了实时的性能数据,如类加载、垃圾回收、编译器等信息</li>
<li>MAT:用于分析堆转储文件的工具,可以帮助识别内存泄漏和优化内存使用</li>
<li>jconsole:可以监控JVM的内存使用、垃圾回收、线程、类加载等信息</li>
<li>VisualVM:可实时显示 JVM 的内存使用、垃圾回收、类加载等信息,也可以分析 Heap Dump 等.</li>
<li>Arthas:一个强大的Java 诊断工具,提供了实时监控和分析功能。通过命令行界面,可以查看 的状态、监控方法调用、追踪 SQL 查询、分析性能瓶颈等。</li>
</ul>
<h2 id="如何在-java-中进行内存泄漏分析">如何在 Java 中进行内存泄漏分析?</h2>
<p>先确认是否真的发生了内存泄漏,即观察内存使用情况。<br>
利用 jstat 命令(<code>jstat -gc <pid> <interal in ms></code>)来观察 gc 概要信息,如果发现,GC 后内存并没有明显的减少目还是持续增加持续触发gc,那说明内存泄漏的概率很大。</p>
<p>此时可以利用 jmap(<code>jmap -dump:format=b,fi1e=heapdump.hprof <pid></code> )生成 heap dump,然后将其导入 Ecdipse MAT 或者 VsuaVM 工具内进行分析,通过大量内存的占用可以找到对应的对象。</p>
<p>通过对象找到对应的代码分析,确认是否可能存在内存泄漏的场景,最终修复代码,解决内存泄漏的问题。</p>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top</p><br><br>
来源:https://www.cnblogs.com/sevencoding/p/18980820
頁:
[1]