• Java内存区域与内存溢出异常


    运行时数据区域

    JVM执行java程序的时候有一个运行时数据区,每个区域有自己的作用,了解这些区域有助于我们理解JVM。JVM运行时数据区如图所示:

    运行时数据区域

    程序计数器

    该区域是线程私有的,字节码解释器通过改变程序计数器来获取下一条指令来执行程序,每一个线程都有一个独立的程序计数器。在执行java方法的时候,计数器记录的是虚拟机字节码指令的地址,执行本地Native方法的时候,计数器的值为空,程序计数器是没有OutOfMemoryError的现象的

    Java虚拟机栈

    虚拟机栈也是线程私有的,是Java方法执行的内存模型,方法执行的时候会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法的执行就是一个栈帧在虚拟机栈中入栈出栈的过程。

    虚拟机栈有两种异常情况:

    • 线程请求的栈深度大于JVM允许的深度,会抛出StackOverflowError异常
    • 如果虚拟机可以动态扩展,那么扩展到无法申请足够的内存时,会抛出OutOfMemoryError异常

    本地方法栈

    和虚拟机栈差不多,一样有虚拟机栈的两种异常情况

    Java堆

    所有线程共享,内存管理中最大的一块,用于存放对象实例,几乎所有的对象实例都在这里分配,垃圾收集器管理的重点区域,当Java堆无法再扩展时,将抛出OutOfMemoryError异常。

    方法区

    所有线程共享,存储已经被虚拟机加载的类信息、常量、静态变量等数据。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

    运行时常量池

    用于存放编译期生成的各种字面量与符号引用,一样可能抛出OutOfMemoryError异常。

    直接内存

    不是运行时数据区的一部分,可以直接使用Native库分配堆外内存,一样可能抛出OutOfMemoryError异常。

    HotSpot虚拟机对象

    对象的创建

    平常我们创建对象一般使用New这个关键字,实际上是一个很复杂的过程

    1. New指令发出来后,虚拟机会去检查这个指令参数能否在常量池找到一个类的符号引用
    2. 检查这个符号引用代表的类是否已被加载、解析、初始化、没有就执行相应的类加载过程
    3. 虚拟机为新生对象分配内存
    4. 对分配的内存空间初始化为零值
    5. 执行init方法,按照程序员的意愿进行初始化

    分配内存的方式:

    指针碰撞:内存规整,只需从空闲内存中分配一块

    空闲列表:内存不是规整的,从空闲列表中找到足够大内存空间进行分配

    PS:内存是后规整和垃圾收集器的方式有关

    对象的内存布局

    对象内存布局可以分为3块区域:对象头、实例数据、对齐填充

    对象头分为两部分,一部分用于存储对象自身的运行时数据:

    储存内容 标志位 状态
    对象哈希码、对象分代年龄 01 未锁定
    指向锁记录的指针 00 轻量级锁定
    指向重量级锁的指针 10 重量级锁定
    空、不需要记录信息 11 GC标记
    偏向线程ID、偏向时间戳、对象分代年龄 01 可偏向

    另外一部分是类型指针,虚拟机通过这个指针确定这个对象是哪个类的实例。

    对象的访问定位

    Java程序通过栈上的引用来操作堆上的具体对象。

    目前主流的访问方式有使用句柄和直接指针:

    • 句柄访问的方式,java堆会划分出一块内存来作为句柄池,栈上的引用储存的就是对象的句柄地址。句柄地址中包括了对象的实例数据与类型数据的具体地址信息
    • 直接指针访问,栈上的引用储存的就是对象的地址

    句柄的优点:对象被移动,只会修改句柄中的实例数据的指针,引用不需要修改

    直接访问的优点:速度更快,减少时间开销

    测试内存异常

    Java堆溢出

    Java堆用于存储对象实例,不断创建对象,就可以测试到现象

     /**
       * VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
       * @author Axe
      **/
    import java.util.ArrayList;
    import java.util.List;
    
    public class HeapOOM {
      
        static class OOMObject {
    
        }
        public static void main(String[] args) {
            List<OOMObject> list = new ArrayList<>();
            while (true){
                list.add(new OOMObject());
            }
        }
    }
    
    

    运行结果:

    java.lang.OutOfMemoryError: Java heap space
    Dumping heap to java_pid5488.hprof ...
    Heap dump file created [13371418 bytes in 0.049 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	at java.util.Arrays.copyOf(Arrays.java:3210)
    	at java.util.Arrays.copyOf(Arrays.java:3181)
    	at java.util.ArrayList.grow(ArrayList.java:265)
    	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
    	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
    	at java.util.ArrayList.add(ArrayList.java:462)
    	at HeapOOM.main(HeapOOM.java:17
    

    堆内存的OOM异常是最常见的,出现的时候我们可以使用Memory Analyzer工具分析hprof文件,具体的使用方式,网上很多教程、如果都是正常的,就要考虑是不是堆的空间给得不够大。

    虚拟机栈和本地方法栈溢出

    直接测试:

    /**
     * VM Args: -Xss128k
     */
    public class JavaVMStacksOF {
        private int stackLength = 1;
    
        public void stackLeak(){
            stackLength++;
            stackLeak();
        }
    
        public static void main(String[] args) {
            JavaVMStacksOF of = new JavaVMStacksOF();
            try {
                of.stackLeak();
            }catch (Throwable e){
                System.out.println("stack length:"+ of.stackLength);
                throw e;
            }
    
        }
    }
    

    运行结果:

    stack length:981
    Exception in thread "main" java.lang.StackOverflowError
    	at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:8)
    	at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
    	at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
    	at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
    	at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
    	at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
    	at JavaVMStacksOF.stackLeak(JavaVMStacksOF.java:9)
    

    出现StackOverflowError可以阅读堆栈信息查找问题

    方法区和运行时常量池溢出

    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
     */
    public class RuntimeConstantPoolOOM {
        public static void main(String[] args) {
            while (true){
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o,objects);
                    }
                });
                enhancer.create();
            }
        }
    
        static class OOMObject{
    
        }
    
    }
    

    很遗憾,我在java1.8里面没有复制到现象

    本地直接内存溢出

    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    /**
     * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
     */
    public class DirectMemoryOOM {
        public static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) {
            Field unsafeField = Unsafe.class.getDeclaredFields()[0];
            unsafeField.setAccessible(true);
            try {
                Unsafe unsafe = (Unsafe) unsafeField.get(null);
                while (true) {
                    unsafe.allocateMemory(_1MB);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    运行结果:

    
    Exception in thread "main" java.lang.OutOfMemoryError
    	at sun.misc.Unsafe.allocateMemory(Native Method)
    	at DirectMemoryOOM.main(DirectMemoryOOM.java:17)
    

    直接内存溢出后Dump文件会很小,如果程序中使用了NIO,就需要往这方面思考。

  • 相关阅读:
    MySQL Explain详解
    sql查询:存在A表而不在B表中的数据
    mybatis处理集合、数组参数使用in查询
    mysql日期范围查找(两个日期之间的记录)
    MYSQL查询数据表中某个字段包含某个数值
    springboot+jpa分页(Pageable+Page)
    MySQL单表能存储多少条数据?
    nosql几种热门数据库的优缺点及应用场景
    MySQL百万级数据分页查询及优化
    Redis cluster群集操作
  • 原文地址:https://www.cnblogs.com/fengwei23/p/9278292.html
Copyright © 2020-2023  润新知