龙哥网

龙哥网

JVM(一).JAVA内存模型&内存溢出(java jvm内存模型)
2022-03-01

JVM(一).JAVA内存模型&内存溢出

1.概述

java 语言不需要手动释放内存,释放内存的事情交给了虚拟机;我们了解了JAVA的内存模型,才能更好的排查和修正代码上面内存泄露的问题;

2.运行时内存区域

虚拟机在执行Java程序的时候会把内存管理的区域划分成几个区域,每个区域有各自的用途;

2.1程序计数器(Program Counter Register)

可以看做线程的行号指示器;作用是改变这个值来选取下一条需要执行的字节码指令;程序控制流指示器:分支,跳转,循环,异常处理都依赖它;

Java虚拟机的多线程依赖的是线程轮流切换分配处理器时间片来实现多线程;一个处理器(多核中的一个CPU内核) 只能是一个线程,为了线程恢复到正确的执行位置,每个线程都是有独立的计数器,每个线程之间独立存储,互不影响,这类内存区域称之为线程私有内存;

2.2虚拟机栈(VM Stack)

也是线程私有,生命周期和线程同步。虚拟机栈描述的是Java 方法执行的线程内存模型;每个方法被执行的时候,虚拟机都会同步创建一个栈帧 Stack Frame,存储局部变量表,操作数栈,动态连接,方法出口等;方法的调用完成,就相当于栈帧的入栈和出栈;

局部变量表:存放编译期间就知道的基本数据类型,引用对象和 returnAddress (下一条指令的地址);变量存放是槽 Slot 的方式 ;64位的long double 2个slot 其余一个,变量槽slot 的大小又具体的虚拟机实现,可能会不同大小;

2.3本地方法栈(Native Method Stack)

和虚拟机栈差不多,只是为执行的本地 Native方法 的栈;HotSpot 就直接合二为一了;

2.4Java堆(Heap)

JVM管理最大区域,线程共享,存放对象实例;几乎所有对象都是在这里分配内存。可以不是连续的物理内存,但是逻辑上是连续的;可以通过参数-Xms /-Xmx来扩展堆的大小

2.5方法区(Method Area)

各个线程共享区域,存储已经加载的类,常量,静态变量即时编译后的代码缓存;

2.6运行时常量池(Runtime Constant Pool)

方法区的一部分。编译期间各种字面量的符号引用;程序运行期间也可以放进常量池;

2.7直接内存(Direct Memory)

不是运行时数据区的一部分,也不是虚拟机规范说明的内存区域;NIO使用的就是直接内存 基于通道Channel和缓冲区buffer 的IO方式,然后再java里面使用DirectByteBuffer就可以对这块内存进行使用操作;不受JAVA 堆大小的限制

3.堆内对象

3.1 对象的创建

1.new 指令触发:先检测常量池类的符号引用,并检测类是否被加载,解析,初始化过,如果没有,执行类的初始化;

2.分配内存,类加载完成后就知道一个对象的内存大小是多少,如果内存是连续的,那么分配的时候就是内存指针在偏移一下这个内存大小,这种方式指针碰撞Bump The Pointer ,如果内存不是连续的话,虚拟机维护一个表,表示那些内存是可用的,然后分配空间,并更新表的记录,这个表空闲列表Free List,Java 堆的完整性是由所采用的的那种垃圾回收决定的;Serial,ParNew 具有压缩整理过程的垃圾回收器就会使用指针碰撞,CMS 回收器就是空闲列表方式;

3.设置对象头,对象是什么类的实例,如何找到类的元数据,对象哈希码,GC年代,对象锁

4.虚拟机的角度看,对象已经创建完成。但是Java程序的角度来看,对象创建才开始,init 指令还没有执行(构造方法),所有字段都是零值;java编译器在new 关键字是会生产出两条指令 NEW , INVOKESPECIAL 如下 ,INVOKESPECIAL 指令会去执行构造方法

//java 
public static void main(String[] args) { 
        ByteCodeDemo demo = new ByteCodeDemo();
    }
//bytecode
    LINENUMBER 12 L0
    NEW com/jvm/chapter2/icode/ByteCodeDemo
    DUP
    INVOKESPECIAL com/jvm/chapter2/icode/ByteCodeDemo.<init> ()V
    ASTORE 1

3.2 对象的内存布局

对象在Heap 里面的布局有三个部分:对象头,实例数据,对其填充;

3.2.1 对象头: Header 两部分组成

第一部分是对象自身数据:HashCode ,GC 年代,锁状态,线程持有的锁,偏向锁ID,偏向时间戳;

// 对象头的位格式  具有动态定义的数据结构
//  32 bits:
//         哈希值32 bits  GC年龄 4 bits   biased_lock  偏向锁:1是 0 否 , 锁 2bits ;epoch 时间戳 
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)//没有锁的时候
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)//有锁的时候
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
                                           
  enum { locked_value             = 0,
         unlocked_value           = 1,
         monitor_value            = 2,
         marked_value             = 3,
         biased_lock_pattern      = 5
  };

内容 标志位 状态
hash:25 ------------>| age:4 biased_lock:1 lock:2 01 未锁定
锁指针 :JavaThread* 00 锁定 轻量级锁
锁指针 :JavaThread* 10 锁定 锁膨胀 重量级锁
不用记录信息 11 可以GC
JavaThreadid epoch age biased_lock lock 01 偏向锁

第二部分 类型指针 表明这个对象指向它类的类型元数据指针;虚拟机通过这个指针来识别是属于那个类;

3.2.2 实例数据

这个对象真正存储有有效信息,就是代码里面定义的字段内容,父类的也会有。hotspot虚拟机分配的数据顺序;一样长度的分配在一起

long/double int short/char byte/boolean oop(对象指针)

3.2.3 填充数据

hotspot 管理内存都是8字节的准数倍,对象没有8的倍数的时候,就会对其填充到8字节的倍数;

3.3对象访问定位

基本数据类型存储就直接在java 栈的本地变量表。引用类型 reference 存储在堆内存上,但是定位的方式有两种

1.句柄访问方式:GC 的时候不需要改变线程栈里面的reference 地址,只需要改变句柄池里面的地址

  1. 直接访问方式:速度快,节约一次定位的时间开销,因为java对象的应用访问比较频繁,主要是使用第二种;

4.内存溢出 OouOfMemeryError

所用软件下载地址 https://www.eclipse.org/mat/previousReleases.php 数字开头是所需要的jdk最低版本

本次下载版本http://download.eclipse.org/mat/1.8.1/update-site/

4.1堆溢出

/** * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError */
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_pid12144.hprof ...
Heap dump file created [28159839 bytes in 0.084 secs]

4.2 栈溢出

/** * VM Args:-Xss128k */
public class JavaVMStackSOF_1 { 
    private int stackLength = 1;
    public void stackLeak() { 
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) { 
        JavaVMStackSOF_1 oom = new JavaVMStackSOF_1();
        try { 
            oom.stackLeak();
        } catch (Throwable e) { 
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}
stack length:984
Exception in thread "main" java.lang.StackOverflowError

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

OutOfMemeryError: Permgen space

JDK8 以前 是元空间代替了方法区,常量池,比如说是字符串常量池,String::intern(); 如果常量池已经有了 ,就返回引用,否则就添加到常量池;

4.4 本机直接内存溢出

OutOfMemeryError

直接内存 Direct Memery 可以通过-XX:MaxDirectMemerySize 指定;有直接内存导致的内存溢出,在Heap Dump 不会看见,Dump文件会很小,程序中使用的直接内存 Direct Memery 的原因(eg: NIO);

免责声明
本站部分资源来源于互联网 如有侵权 请联系站长删除
龙哥网是优质的互联网科技创业资源_行业项目分享_网络知识引流变现方法的平台为广大网友提供学习互联网相关知识_内容变现的方法。