• 周一. 8月 15th, 2022

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

深入理解Java虚拟机-第三版-第二章JVM 实践 StackOverFlowError OutOfMemoryError

admin

11月 28, 2021

  JVM一些参数
  -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

  作者使用了一些参数来限制JVM的各部分的大小,以快速达到测试目的。这里我想说的是,很多同学说没有条件,工作中没有遇到高并发的场景,其实可以自己搭建一个受限JVM+JMeter+其它必要中间件(如MySQL,Redis等)组合来实现这个目的。

———————————————————————————————
Java堆溢出
  通过设置-XX: +HeapDumpOnOutOf-MemoryError 可以让JVM在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后分析。
  解决思路:
  通过内存映像分析工具对Dump出来的堆转储快照信息进行分析。先确认导致OOM的对象,分清楚到底是Memory Leak还是Memory Overflow。如果是内存泄漏,可进一步通过工具查看泄露对象到GC Roots的引用链,找到泄露对象是通过怎样的路径、与哪些GC Roots相关联才导致垃圾收集器无法回收他们,进而分析内存泄漏代码的具体位置。

public class HeapOOEMTest {
    static class OOEM {
    }

    public static void main(String[] args) {
        List<OOEM> ooemTest = new ArrayList<>();
        while (true) {
            ooemTest.add(new OOEM());
        }
    }
}

———————————————————————————————
Java栈溢出
  1.栈深度大于虚拟机所允许的最大深度,将抛出StackOverFlowError
  2.如果虚拟机栈内存允许动态扩展,当扩展容量无法申请到足够内存时,抛出OOME
  通过设置-Xss128K的方式设置JVM的栈容量可以比较方便地模拟该状态。不同的操作系统和不同版本的JVM栈容量会有所不同,主要取决于OS的内存分页大小限制。
  如果-Xss设置的过小,一些JVM启动时会报错。而在IDEA中,这样的设置可能不会生效。

  无论栈帧太大,还是栈容量太小,当新的栈帧内存无法分配时,HotSpot抛出的都是SOFE
  如果是建立过多线程导致的内存溢出,在不能减少线程数量或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。这种通过“减少内存”的手段来解决内存溢出的方式,如果没有这方面处理经验,一般比较难以想到,这一点读者需要在开发32位系统的多线程应用时注意。

public class StackSOFETest {
    private int stackLength = 1;

    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        StackSOFETest stackSOFETest = new StackSOFETest();
        try {
            stackSOFETest.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + stackSOFETest.stackLength);
            throw e;
        }
    }
}

———————————————————————————————
方法区和运行时常量池溢出
  在JDK 6或更早之前的HotSpot虚拟机中,常量池都是分配在永久代中,我们可以通过-XX:PermSize和-XX:MaxPermSize限制永久代的大小,即可间接限制其中常量池的容量。

public class MethodAreaOOEMTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        int i = 0;
        while (true) {
            set.add(String.valueOf(i++).intern());
        }
    }
}

  在JDK8或者更高版本的JDK结果不同,如果设置了这两个参数,JVM还会给出提示:

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=6M; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=6M; support was removed in 8.0

  String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

  在JDK 8以后,永久代便完全退出了历史舞台,元空间作为其替代者登场。
HotSpot还是提供了一些参数作为元空间的防御措施,避免因运行时生成大量的动态类而造成的破坏性结果(JDK1.7之前表现为PermGen space OOME)
  -XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。
  -XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值。(动态的)
  -XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio,用于控制最大的元空间剩余容量的百分比。
———————————————————————————————
直接内存溢出
  直接内存(Direct Memory)的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致。
  思路:由直接内存导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况,如果读者发现内存溢出之后产生的Dump文件很小,而程序中又直接或间接使用了DirectMemory(典型的间接使用就是NIO),那就可以考虑重点检查一下直接内存方面的原因了。

public class DirectMemoryOOEMTest {
    public static void main(String[] args) throws IllegalAccessException {
        Field field = Unsafe.class.getDeclaredFields()[0];
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        while (true) {
            unsafe.allocateMemory(1024 * 1024);
        }
    }
}

发表回复

您的电子邮箱地址不会被公开。