内存结构

总体内存结构

名称 线程私有/共享 功能 大小 异常
程序计数器 私有 保存当前线程执行方法 通常固定大小 不会
JVM栈 私有 方法的栈帧 -Xss StackOverflowError,OutOfMemoryError
本地方法栈 私有 存储native方法信息 通常会固定大小 StackOverflowError,OutOfMemoryError
共享 存储对象和数组 -Xms初始堆值,-Xmx最大堆值 OutOfMemoryError
方法区 共享 存储类结构 常量 静态变量 -XX参数 OutOfMemoryError
运行时常量池 共享 常量池运行时表示 从属于方法区 OutOfMemoryError

批注 2020-07-29 083921 批注 2020-07-29 084922 截图录屏_选择区域_20200918152033

概览

截图录屏_选择区域_20200918155103

PC 程序计数器

用来存放执行指令的偏移量和行号指示器等

Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器

存放指令位置 虚拟机的运行,类似于这样的循环:

while( not end ) {

​	取PC中的位置,找到对应位置的指令;
​	执行该指令;
​	PC ++;

}

JVM Stack

每个线程私有

截图录屏_选择区域_20200918153359

  1. Frame - 每个方法对应一个栈帧
    1. Local Variable Table 局部变量表 存放方法参数和局部变量的区域
    2. Operand Stack 操作栈 各种指令往栈中写入或者读取信息 对于long的处理(store and load),多数虚拟机的实现都是原子的 jls 17.7,没必要加volatile
    3. Dynamic Linking 动态连接 常量池中一个对当前方法的引用 https://blog.csdn.net/qq_41813060/article/details/88379473 jvms 2.6.3
    4. return address 方法返回地址 a() -> b(),方法a调用了方法b, b方法的返回值放在什么地方

本地方法栈

为本地方法服务

方法区

各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

  1. 1.8之前被HotSpot使用一个永久代实现 字符串常量位于永久代 FGC不会清理 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载 但是类型卸载条件十分苛刻 大小启动的时候指定,不能变
  2. Meta Space (>=1.8) 字符串常量位于堆 会触发FGC清理 不设定的话,最大就是物理内存

运行时常量池

是方法区的一部分

用于存放编译期生成的各种字面量与符号引用

Java语言并不要求常量一定只有编译期才能产生,运行期间也可以将新的常量放入池中,比如String.intern()

直接内存

JVM可以直接访问的内核空间的内存 (OS 管理的内存) NIO , 提高效率,实现zero copy

一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据

本机直接内存的分配不会受到Java堆大小的限制

对象的创建

对象内存布局

批注 2020-05-11 111756 屏幕截图 2020-09-16 152736

  1. 对象头
    1. 对象本身运行时的数据 哈希吗 GC标记 锁信息等等
    2. 类元信息:指向Class的指针
  2. 实例数据:实例成员变量及所有可见的父类成员变量
  3. 对齐填充

锁升级:无锁状态 -> 偏向锁 -> 自旋锁 -> 重量级锁

32位虚拟机对象头:批注 2020-07-27 091839

64位虚拟机对象头:批注 2020-05-12 145237

64 位的 Java 虚拟机中,对象头的标记字段占 64 位,而类型指针又占了 64 位,开启指针压缩后,对象头中的类型指针会被压缩成 32 位,使得对象头的大小从 16 字节降至 12 字节,虚拟机通过使用一个 32 位偏移量来表示对象引用,从而降低指针所需要的数据位数,这意味着虚拟机需要对对象的内存进行填充以及重新排列变量的顺序以实现对齐,

Hotspot开启内存压缩的规则

UseCompressedOopsClassPointers

UseCompressedOops

  1. 4G以下,直接砍掉高32位
  2. 4G - 32G,默认开启内存压缩 ClassPointers Oops
  3. 32G,压缩无效,使用64位

对象定位

批注 2020-05-11 134427

使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销

对象实例化

字节码角度

Object ref = new Object()

得到字节码:

stack=2, locals=2, args_size=1
   0: new           #2                  // class java/lang/Object
   3: dup
   4: invokespecial #1                  // Method java/lang/Object."<init>":()V
   7: astore_1
   8: return

new:

  1. 如果类不存在 就先进行类加载
  2. 为所有属性值分配内存
  3. 对所有属性值进行0值初始化
  4. 最后将指向实例对象的引用变量压入虚拟机栈顶

dup:

  1. 在栈顶复制实例对象的引用变量
    1. 复制出来的这个变量用来作为句柄调用相关方法
    2. 早一点的那个变量则是用来赋值

invokespecial:

  1. 通过上面dup复制的变量调用对象的<init> 方法

从执行步骤

  1. 确认类信息是否存在于metaspace 否则使用类加载器加载类 并生成相关Class对象
  2. 计算对象占用的内存空间(实例数据) 接下来在堆内存划分一块空间进行分配 为对象分配内存时 需要进行同步操作
  3. 设定成员变量的默认值
  4. 设置对象头 哈希吗 GC信息等等
  5. 执行init方法 初始化成员变量 执行初始化代码块等等

查看堆内存使用情况

jstat -class pid # 查看加载的类
jstat -gc pid # 查看垃圾回收情况

内存分析

jmap -histo pid # 查看所有对象
jmap -histo:live pid # 查看所有存活对象
jmap -dump:format=b,file=filename pid # 导出dump文件
jhat filename

内存溢出定位与分析

Java 堆溢出

java -Xmx8m -Xms8m -XX:+HeapDumpOnOutOfMemoryError
List<Object> list = new ArrayList<>();
while (true){
    list.add(new Object());
}

虚拟机栈和本地方法栈移除

-Xss256k
public class JVMSOFWithMinStack {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) throws Throwable {
        JVMSOFWithMinStack oom = new JVMSOFWithMinStack();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength); // 3183
            throw e;
        }
    }
}
public class JVMSOFWithMuchParams {
    private static int stackLength = 0;

    public static void test() {
        long unused1, unused2, unused3, unused4, unused5,
                unused6, unused7, unused8, unused9, unused10,
                unused11, unused12, unused13, unused14, unused15,
                unused16, unused17, unused18, unused19, unused20,
                unused21, unused22, unused23, unused24, unused25,
                unused26, unused27, unused28, unused29, unused30,
                unused31, unused32, unused33, unused34, unused35,
                unused36, unused37, unused38, unused39, unused40,
                unused41, unused42, unused43, unused44, unused45,
                unused46, unused47, unused48, unused49, unused50,
                unused51, unused52, unused53, unused54, unused55,
                unused56, unused57, unused58, unused59, unused60,
                unused61, unused62, unused63, unused64, unused65,
                unused66, unused67, unused68, unused69, unused70,
                unused71, unused72, unused73, unused74, unused75,
                unused76, unused77, unused78, unused79, unused80,
                unused81, unused82, unused83, unused84, unused85,
                unused86, unused87, unused88, unused89, unused90,
                unused91, unused92, unused93, unused94, unused95,
                unused96, unused97, unused98, unused99, unused100;
        stackLength++;
        test();
        unused1 = unused2 = unused3 = unused4 = unused5 =
                unused6 = unused7 = unused8 = unused9 = unused10 =
                        unused11 = unused12 = unused13 = unused14 = unused15 =
                                unused16 = unused17 = unused18 = unused19 = unused20 =
                                        unused21 = unused22 = unused23 = unused24 = unused25 =
                                                unused26 = unused27 = unused28 = unused29 = unused30 =
                                                        unused31 = unused32 = unused33 = unused34 = unused35 =
                                                                unused36 = unused37 = unused38 = unused39 = unused40 =
                                                                        unused41 = unused42 = unused43 = unused44 = unused45 =
                                                                                unused46 = unused47 = unused48 = unused49 = unused50 =
                                                                                        unused51 = unused52 = unused53 = unused54 = unused55 =
                                                                                                unused56 = unused57 = unused58 = unused59 = unused60 =
                                                                                                        unused61 = unused62 = unused63 = unused64 = unused65 =
                                                                                                                unused66 = unused67 = unused68 = unused69 = unused70 =
                                                                                                                        unused71 = unused72 = unused73 = unused74 = unused75 =
                                                                                                                                unused76 = unused77 = unused78 = unused79 = unused80 =
                                                                                                                                        unused81 = unused82 = unused83 = unused84 = unused85 =
                                                                                                                                                unused86 = unused87 = unused88 = unused89 = unused90 =
                                                                                                                                                        unused91 = unused92 = unused93 = unused94 = unused95 =
                                                                                                                                                                unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }

    public static void main(String[] args) {
        try {
            test();
        } catch (Error e) {
            System.out.println("stack length:" + stackLength); // 127
            throw e;
        }
    }
}

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

在JDK7和7之前如果大量创建String.intern或者动态类 由于类的回收条件苛刻 极有可能造成OOM

但在JDK8之后 这些问题就没有了

本机直接内存溢出

-Xmx20M -XX:MaxDirectMemorySize=10M
public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}

直接内存导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常

分析线程执行情况

jstack pid

两个进程互相等待对方,一直阻塞下去

Found one Java-level deadlock:
=============================
"Thread-0":
  waiting to lock monitor 0x000002292014eb00 (object 0x00000000ffea7640, a java.lang.Object),
  which is held by "Thread-1"
"Thread-1":
  waiting to lock monitor 0x000002292014ea00 (object 0x00000000ffea7630, a java.lang.Object),
  which is held by "Thread-0"

Java stack information for the threads listed above:
===================================================
"Thread-0":
        at Main.lambda$main$0(Main.java:24)
        - waiting to lock <0x00000000ffea7640> (a java.lang.Object)
        - locked <0x00000000ffea7630> (a java.lang.Object)
        at Main$$Lambda$14/0x0000000800ba4840.run(Unknown Source)
        at java.lang.Thread.run(java.base@13/Thread.java:830)
"Thread-1":
        at Main.lambda$main$1(Main.java:37)
        - waiting to lock <0x00000000ffea7630> (a java.lang.Object)
        - locked <0x00000000ffea7640> (a java.lang.Object)
        at Main$$Lambda$15/0x0000000800ba4c40.run(Unknown Source)
        at java.lang.Thread.run(java.base@13/Thread.java:830)

Found 1 deadlock.

JMX

MX(Java Management Extensions)是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,实际上,用户可以在任何Java应用程序中使用这些代理和服务实现管理