Java 虚拟机程序员

【Java 虚拟机笔记】内存模型相关整理

2019-02-22  本文已影响5人  58bc06151329

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

1. 运行时数据区域

内存模型

1.1 程序计数器(Program Counter Register)

public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc_w         #2                  
         3: dup           
         4: astore_1      
         5: monitorenter 
         6: aload_1       
         7: monitorexit
         8: goto          16
        11: astore_2      
        12: aload_1       

1.2 Java 虚拟机栈(VM Stacks)

Java 虚拟机栈概念模型

1.2.1 栈帧(Frame)

1.2.1.1 局部变量表(Local Variable Table)


局部变量表 Slot 的复用对垃圾回收的影响

public class Test {

    public static void main(String[] args) {
        byte[] placeholder = new byte[64 * 1024 * 1024];
        System.gc();
    }
}
//[GC (System.gc())  69191K->66104K(231936K), 0.0009956 secs]
//[Full GC (System.gc())  66104K->65921K(231936K), 4.6295968 secs]

局部变量表

Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
5 4 1 placeholder [B
public class Test {

    public static void main(String[] args) {
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        System.gc();
    }
}
//[GC (System.gc())  69191K->66104K(231936K), 0.0013618 secs]
//[Full GC (System.gc())  66104K->65921K(231936K), 4.0038869 secs]

局部变量表

Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
public class Test {

    public static void main(String[] args) {
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        int a = 0;
        System.gc();
    }
}
//[GC (System.gc())  69191K->66008K(231936K), 0.0015096 secs]
//[Full GC (System.gc())  66008K->385K(231936K), 0.0045977 secs]   --- 回收后剩余 385K

局部变量表

Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
7 4 1 a I
public class Test {

    public static void main(String[] args) {
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
            placeholder = null;
        }
        System.gc();
    }
}
//[GC (System.gc())  69191K->66040K(231936K), 0.0012288 secs]
//[Full GC (System.gc())  66040K->385K(231936K), 0.0095720 secs]

局部变量表

Start Length Slot Name Signature
5 2 1 placeholder [B
0 11 0 args [Ljava/lang/String;

1.2.1.2 操作栈(Operand Stack)

1.2.1.3 动态连接(Dynamic Linking)

1.2.1.4 返回地址(Return Address)

1.3 本地方法栈(Native Method Stack)

线程调用 Java 方法和本地方法时的栈

1.4 Java 堆(Java Heap)

[GC (System.gc()) [PSYoungGen: 3655K->432K(70656K)] 69191K->65968K(231936K), 0.0013816 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 432K->0K(70656K)] [ParOldGen: 65536K->385K(161280K)] 65968K->385K(231936K), [Metaspace: 3433K->3433K(1056768K)], 0.0045779 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 70656K, used 609K [0x0000000771e00000, 0x0000000776c80000, 0x00000007c0000000)
  eden space 60928K, 1% used [0x0000000771e00000,0x0000000771e98690,0x0000000775980000)
  from space 9728K, 0% used [0x0000000775980000,0x0000000775980000,0x0000000776300000)
  to   space 9728K, 0% used [0x0000000776300000,0x0000000776300000,0x0000000776c80000)
 ParOldGen       total 161280K, used 385K [0x00000006d5a00000, 0x00000006df780000, 0x0000000771e00000)
  object space 161280K, 0% used [0x00000006d5a00000,0x00000006d5a60478,0x00000006df780000)
 Metaspace       used 3440K, capacity 4494K, committed 4864K, reserved 1056768K
  class space    used 332K, capacity 386K, committed 512K, reserved 1048576K
区域名称 说明
新生代(Young Generation) 主要用来存放新生的对象。
老年代(Old Generation 或者 Tenured Generation) 主要存放应用程序声明周期长的内存对象。
参数 说明
-Xms 初始堆大小。如:-Xms256m。
-Xmx 最大堆大小。如:-Xmx512m。
-Xmn 新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%。
-Xss JDK 1.5+ 每个线程堆栈大小为 1M,一般来说栈不是很深的话, 1M 绝对够用了。
-XX:NewRatio 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的 1/3,老年代占 2/3。
-XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10。
-XX:PermSize 永久代(方法区)的初始大小。
-XX:MaxPermSize 永久代(方法区)的最大值。
-XX:+PrintGCDetails 打印 GC 信息。
-XX:+HeapDumpOnOutOfMemoryError 让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析使用。

1.5 方法区(Method Area)

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

字符串常量池(String Constant Pool)

Class 常量池(Class Constant Pool)


1.7 堆外内存/直接内存(Direct Memory)

DirectByteBuffer 对象

DirectByteBuffer(int cap) {                 
 
        super(-1, 0, cap, cap);
        //内存是否按页分配对齐
        boolean pa = VM.isDirectMemoryPageAligned();
        //获取每页内存大小
        int ps = Bits.pageSize();
        //分配内存的大小,如果是按页对齐方式,需要再加一页内存的容量
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        //用Bits类保存总分配内存(按页分配)的大小和实际内存的大小
        Bits.reserveMemory(size, cap);
 
        long base = 0;
        try {
           //在堆外内存的基地址,指定内存大小
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        //计算堆外内存的基地址
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
}

private static class Deallocator implements Runnable  {
    private static Unsafe unsafe = Unsafe.getUnsafe();
    private long address;
    private long size;
    private int capacity;
    private Deallocator(long address, long size, int capacity) {
        assert (address != 0);
        this.address = address;
        this.size = size;
        this.capacity = capacity;
    }
 
    public void run() {
        if (address == 0) {
            // Paranoia
            return;
        }
        unsafe.freeMemory(address);
        address = 0;
        Bits.unreserveMemory(size, capacity);
    }
}
public static Cleaner create(Object ob, Runnable thunk) {
    if (thunk == null)
        return null;
    return add(new Cleaner(ob, thunk));
}

public void clean() {
    if (!remove(this))
        return;
    try {
        thunk.run();//此处会调用 Deallocator
    } catch (final Throwable x) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (System.err != null)
                        new Error("Cleaner terminated abnormally", x)
                            .printStackTrace();
                    System.exit(1);
                    return null;
                }});
    }
}
初始化堆外内存 DirectByteBuffer 对象被 GC 回收
DirectByteBuffer(int cap) {                   // package-private
......
        Bits.reserveMemory(size, cap);
......
static void reserveMemory(long size, int cap) {

        if (!memoryLimitSet && VM.isBooted()) {
            maxMemory = VM.maxDirectMemory();
            memoryLimitSet = true;
        }

        // optimist!
        if (tryReserveMemory(size, cap)) {
            return;
        }

        final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();

        // retry while helping enqueue pending Reference objects
        // which includes executing pending Cleaner(s) which includes
        // Cleaner(s) that free direct buffer memory
        while (jlra.tryHandlePendingReference()) {
            if (tryReserveMemory(size, cap)) {
                return;
            }
        }

        // trigger VM's Reference processing
        System.gc();

        // a retry loop with exponential back-off delays
        // (this gives VM some time to do it's job)
        boolean interrupted = false;
......

堆外内存默认值大小

public static void saveAndRemoveProperties(Properties var0) {
        if (booted) {
            throw new IllegalStateException("System initialization has completed");
        } else {
            savedProps.putAll(var0);
            String var1 = (String)var0.remove("sun.nio.MaxDirectMemorySize");
            if (var1 != null) {
                if (var1.equals("-1")) {
                    directMemory = Runtime.getRuntime().maxMemory(); //获取默认堆外内存
                } else {
                    long var2 = Long.parseLong(var1);
                    if (var2 > -1L) {
                        directMemory = var2;
                    }
                }
            }

            var1 = (String)var0.remove("sun.nio.PageAlignDirectMemory");
            if ("true".equals(var1)) {
                pageAlignDirectMemory = true;
            }

            var1 = var0.getProperty("sun.lang.ClassLoader.allowArraySyntax");
            allowArraySyntax = var1 == null ? defaultAllowArraySyntax : Boolean.parseBoolean(var1);
            var0.remove("java.lang.Integer.IntegerCache.high");
            var0.remove("sun.zip.disableMemoryMapping");
            var0.remove("sun.java.launcher.diag");
            var0.remove("sun.cds.enableSharedLookupCache");
        }
}

什么时候使用堆外内存

不能大面积使用堆外内存

2. HotSpot 虚拟机对象

普通对象的创建

对象的内存布局

对象头

对象头信息

实例数据

对齐填充

对象的访问定位

3. OutOfMemoryError 异常

Java 堆溢出

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

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

本机直接内存溢出

参考资料

http://www.cnblogs.com/noKing/p/8167700.html
https://blog.csdn.net/ychenfeng/article/details/77247807
https://blog.csdn.net/zm13007310400/article/details/77534349
https://www.jianshu.com/p/7a5fa718eb59?utm_campaign
https://www.cnblogs.com/moonandstar08/p/5107648.html
https://yq.aliyun.com/articles/2948
https://blog.csdn.net/wangtaomtk/article/details/52267548
https://blog.csdn.net/q5706503/article/details/84640762

上一篇 下一篇

猜你喜欢

热点阅读