除了程序计数器外,虚拟机内存在其他几个运行时区域都有发生OutOfMemoryError异常的可能。

Java堆溢出

设置Idea堆的大小为20MB,不可扩展(-Xms参数与最大值-Xmx参数设置为一样,避免自动扩展)

-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

运行以下代码:

package memory;

import java.util.ArrayList;
import java.util.List;

public class HeepOOM {
    static class OOMObject{

    }

    public  static  void  main(String[] args){
        List<OOMObject> list = new ArrayList<>();
        while (true){
            list.add(new OOMObject());
        }
    }
}
抛出错误:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.base/java.util.Arrays.copyOf(Arrays.java:3512)
    at java.base/java.util.Arrays.copyOf(Arrays.java:3481)
    at java.base/java.util.ArrayList.grow(ArrayList.java:237)
    at java.base/java.util.ArrayList.grow(ArrayList.java:244)
    at java.base/java.util.ArrayList.add(ArrayList.java:454)
    at java.base/java.util.ArrayList.add(ArrayList.java:467)
    at memory.HeepOOM.main(HeepOOM.java:14)

解决这个异常重点是确认内存中的对象是否是必要的,也就是区分是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)

  • 内存泄漏:程序申请内存后,无法释放已申请的内存空间,多次内存泄漏堆积后的后果就是内存溢出
  • 内存溢出:程序申请内存时,没有足够的内存供申请者使用

如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Root相关联导致垃圾回收器无法自动回收他们。

如果不存在泄漏,则就应该检查虚拟机堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少长期运行期的内存消耗。

虚拟机栈和本地方法栈溢出
  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,则抛出StackOverflowError异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存,则抛出OutOfMemoryError异常。
package memory;

public class JavaVmStackSOF {
    private int stackLength = 1;

    public  void  stackLeak(){
        stackLength++;
        stackLeak();
    }
            /**
     * -Xss180K -设置每个线程分配180K内存
     * @param args
     * @throws Throwable
     */
    public static void main(String[] args) throws Throwable{
        JavaVmStackSOF oom = new JavaVmStackSOF();
        try{
            oom.stackLeak();
        }catch (Throwable e){
            System.out.println("stack length:"+oom.stackLength);
            throw  e;
        }
    }
}

运行结果:

Exception in thread "main" java.lang.StackOverflowError
stack length:1554
    at memory.JavaVmStackSOF.stackLeak(JavaVmStackSOF.java:7)
package memory;

public class JavaVMStackOOM {

   private void dontStop(){
       while (true){

       }
   }

   public  void  stackLeakByThread(){
       while (true){
           Thread thread = new Thread(new Runnable() {
               @Override
               public void run() {
                   dontStop();
               }
           });
           thread.start();
       }
   }

   /**
    * -Xss2M -设置每个线程分配2M内存
    * 最终会耗尽所有内存,导致没有足够的内存创建线程而抛出异常:OutOfMemoryError
    * @param args
    * @throws Throwable
    */
   public static void main(String[] args) throws Throwable{
       JavaVMStackOOM oom = new JavaVMStackOOM();
       oom.stackLeakByThread();
   }
}

运行结果

Exceptuib in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

运行时常量池溢出
运行时常量池属于方法区,因此可以通过以下方式模拟:
java7可以通过设置:-XX:PermSize=10M -XX:MaxPermSize=10M 来限定方法区。
java8之后永久代被移除,-XX:PermSize、-XX:MaxPermSize已经被移除;可以使用:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M (元空间代替)
类型信息、字段、方法、常量保存在元空间。

package main.java.loadclass;

import java.util.ArrayList;
import java.util.List;

public class RuntimeConstantPoolOOM {

            /**
     * JAVA7 下运行
     * @param args
     */
    public static void main(String[] args){
        //使用LIST保持着常量池引用,避免Full GC 回收常量池行为
        List<String> list = new ArrayList<>();
        //10MB的PermSize在integer范围内足够产生OOM了
        int i = 0;
        while (true){
            list.add(String.valueOf(i++).intern());
        }
    }
}

抛出以下异常:java.lang.OutOfMemoryError:PermGen space

方法区溢出
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。一个类如果被垃圾回收器回收掉,判定条件非常苛刻,在经常动态生成大量Class的应用中,需要特别注意类的回收状态。

本机直接内存溢出
DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样。
DirectByteBuffer是Java用于实现堆外内存的一个重要类,我们可以通过该类实现堆外内存的创建、使用和销毁。DirectByteBuffer跑出内存溢出异常时并没有真正整整向操作系统申请分配内存,而是通过计算得知内存无法分配,手动跑出异常。
使用Unsafe类的allocateMemory方法是真正分配内存。

package main.java.loadclass;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    /**
     * -Xmx20M -XX:MaxDirectMemorySize=10M
     * @param args
     * @throws Exception
     */
    public  static  void main(String[] args) throws Exception{
        Field unsefeField = Unsafe.class.getDeclaredFields()[0];
        unsefeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsefeField.get(null);
        while (true){
            unsafe.allocateMemory(_1MB);
        }
    }
}

返回结果:

Exception in thread "main" java.lang.OutOfMemoryError
    at java.base/jdk.internal.misc.Unsafe.allocateMemory(Unsafe.java:616)
    at jdk.unsupported/sun.misc.Unsafe.allocateMemory(Unsafe.java:462)
    at main.java.loadclass.DirectMemoryOOM.main(DirectMemoryOOM.java:16)

 

posted @ 2022-05-03 13:17 温暖如太阳 阅读(396) 评论(0) 推荐(1) 编辑
摘要: 程序计数器、虚拟机栈、本地方法栈三个区域随着线程的创建而创建、执行完成销毁,栈中的栈帧随着放大的进入和退出执行入栈与出栈,每个栈帧分配多少内存基本上是在类结构确定下来时已知,因此这几个区域的内存分配与回收都具备确定性。Java堆中存放的所有对象的实例,只有在程序运行期间我们才会知道会创建哪些对象,这 阅读全文
posted @ 2022-05-02 11:20 温暖如太阳 阅读(311) 评论(0) 推荐(0) 编辑
摘要: 类从被加载到虚拟机内存中开始,到卸载出内存截止,整个生命周期包括:加载、验证、准备、解析,初始化、使用、卸载七个阶段。其中验证、准备、解析三个部分统称为连接。 类初始化情况: 遇到new、getstatic、putstatic 或 invokestatic 这4条字节码指令时,如果没有初始化,则需要 阅读全文
posted @ 2022-04-10 13:21 温暖如太阳 阅读(86) 评论(0) 推荐(0) 编辑
摘要: .NET多年以前已经开始支持Docker,但由于国内.net现状,生产过程中几乎用不到docker支持,趁着有点时间捣鼓下~。 先期工作 1、首先安装 Docker Desktop 2、安装Visual Studio 创建项目 使用VS分别创建一个ASP.NET Core Api(WebApplic 阅读全文
posted @ 2022-04-09 19:26 温暖如太阳 阅读(1680) 评论(2) 推荐(7) 编辑
摘要: 在开始之前我们先了解几个名词: 1、什么是函数式编程:函数式编程属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用,可以说是面向过程的程序设计。 2、函数式编程的优势: 1)函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。 2)易于"并发编程" 。 3 阅读全文
posted @ 2021-01-12 08:38 温暖如太阳 阅读(276) 评论(0) 推荐(0) 编辑
摘要: 先看段代码: 1 for (int i = 0; i < 10; i++) 2 { 3 Task.Factory.StartNew(()=>Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} ~ {i}")); 4 } 从代码上可以看 阅读全文
posted @ 2021-01-02 19:23 温暖如太阳 阅读(1004) 评论(0) 推荐(0) 编辑
摘要: 记忆化,是一种为了提高应用程序性能的FP技术。程序加速是通过缓存函数的结果实现的,避免了重复计算带来的额外开销。 1、现在我们使用Dictionary作为缓存结构 1 public static Func<T, R> Memoize<T, R>(Func<T, R> func) 2 where T 阅读全文
posted @ 2021-01-01 20:38 温暖如太阳 阅读(361) 评论(0) 推荐(1) 编辑
摘要: 什么是赫夫曼树? 赫夫曼树(Huffman Tree)是指给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小。哈夫曼树(也称为最优二叉树)是带权路径长度最短的树,权值较大的结点离根较近。 1 public class HNode<T> 2 { 3 public HNode() 阅读全文
posted @ 2020-12-19 21:01 温暖如太阳 阅读(201) 评论(0) 推荐(0) 编辑
摘要: 为什么线索化二叉树? 对于二叉树的遍历,我们知道每个节点的前驱与后继,但是这是建立在遍历的基础上,否则我们只知道后续的左右子树。现在我们充分利用二叉树左右子树的空节点,分别指向当前节点的前驱、后继,便于快速查找树的前驱后继。 不多说,直接上代码: /// <summary> /// 线索二叉树 节点 阅读全文
posted @ 2020-12-16 21:11 温暖如太阳 阅读(163) 评论(0) 推荐(0) 编辑
摘要: 对比上一篇文章“顺序存储二叉树”,链式存储二叉树的优点是节省空间。 二叉树的性质: 1、在二叉树的第i层上至多有2i-1个节点(i>=1)。 2、深度为k的二叉树至多有2k-1个节点(k>=1)。 3、对任何一棵二叉树T,如果其终结点数为n0,度为2的节点数为n2,则n0=n2+1。 4、具有n个节 阅读全文
posted @ 2020-12-06 16:04 温暖如太阳 阅读(319) 评论(0) 推荐(0) 编辑
点击右上角即可分享
微信分享提示