JVM之内存结构

JVM运行时内存结构是怎么的?

image.png
运行时内存划分为:堆、java虚拟机栈、本地方法栈、程序计数器、方法区。

程序计数器

记录当前执行的字节码行号,可以用于线程恢复,改变它可以用于执行下一个字节码指令、跳转等,很明显是线程私有的,占用内存空间很小,是唯一JVM没有规定任何OOM(OutOfMemoryError)的区域。

java虚拟机栈

image.png

用于存放java线程在执行时存放的本地数据,如局部变量表、操作数栈、动态链接(当前方法指向运行时常量池的应用)、返回地址(返回被调用的地方)。是线程私有的,在线程创建的时候会分配默认为1M(通过-Xss指定)。每进入一个方法都会创建一个栈帧,退出一个方法就会销毁一个栈帧。

如果一直进入栈帧不退出栈帧就会出现StackOverflowError。如果在分配虚拟机栈时候没有内存来就会OOM。

本地方法栈

调用JNI(java native interface)时使用到的,虚拟机可以自由实现它,和java虚拟机栈类似,也会发生StackOverflowError和OOM。

用于存放java对象,线程共享的,GC回收的主要区域。可以使用-Xms和-Xmx指定最小和最大空间。如果空间不足会发生OOM。

对象一定放堆中吗?

不一定,即时编译器的存在(缓存编译结果(机器码)不用变解释边执行)使逃逸分析变的简单。

逃逸分析,就是判断对象的作用域是不是当前方法,如果不是说明可能存在逃逸。如果不存在逃逸就分配在栈中,出栈的时候就销毁了不用gc了。可以提高JVM的效率。

我们可以试下,默认是开启逃逸分析的。

/**
 * 逃逸分析-栈上分配
 * -XX:-DoEscapeAnalysis -XX:PrintGc
 */
public class EscapeAnalysisTest {
    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 50000000; i++) {//5000万次---5000万个对象
            allocate();
        }
        System.out.println((System.currentTimeMillis() - start) + " ms");
        Thread.sleep(3000);
    }

    static void allocate() {//逃逸分析(不会逃逸出方法)
        //这个myObject引用没有出去,也没有其他方法使用
        MyObject myObject = new MyObject(2020, 2020.6);
    }

    static class MyObject {
        int a;
        double b;

        MyObject(int a, double b) {
            this.a = a;
            this.b = b;
        }
    }
}
复制代码

直接java -XX:PrintGC 执行class结果:

19 ms
复制代码

直接java -XX:-DoEscapeAnaysis -XX:PrintGC 执行class结果:

[GC (Allocation Failure)  33280K->496K(125952K), 0.0047785 secs]
[GC (Allocation Failure)  33776K->376K(159232K), 0.0008720 secs]
[GC (Allocation Failure)  66936K->408K(159232K), 0.0006539 secs]
[GC (Allocation Failure)  66968K->424K(225792K), 0.0005009 secs]
[GC (Allocation Failure)  133544K->392K(225792K), 0.0006146 secs]
[GC (Allocation Failure)  133512K->456K(354304K), 0.0004851 secs]
[GC (Allocation Failure)  266696K->312K(354304K), 0.0009039 secs]
[GC (Allocation Failure)  266552K->312K(621056K), 0.0003459 secs]
263 ms
复制代码

效率提高的很明显,没有发现gc。

方法区

用于存放类的元信息、常量、静态变量、即时编译后的机器码等数据,线程共享的。如果空间不足会发生OOM。可以通过-XX:MaxDirectMemorySize来设置。

常量池

静态常量池

class文件中存在的,存放编译器生成的过程中存放的各种字面变量和引用(符号引用,具体可以见下面的字节码),能节约字节码空间。随便找个class文件使用javap -v查看就能找到。

Constant pool:
   #1 = Methodref          #16.#42        // java/lang/Object."<init>":()V
   #2 = Class              #43            // com/study/juc/SyncUnFairLockTest$DiningRoom
   #3 = Methodref          #2.#44         // com/study/juc/SyncUnFairLockTest$DiningRoom."<init>":(Lcom/study/juc/SyncUnFairLockTest$1;)V
   #4 = Class              #45            // java/lang/Thread
   #5 = InvokeDynamic      #0:#50         // #0:run:(Lcom/study/juc/SyncUnFairLockTest$DiningRoom;)Ljava/lang/Runnable;
   #6 = Class              #51            // java/lang/StringBuilder
   #7 = Methodref          #6.#42         // java/lang/StringBuilder."<init>":()V
   #8 = String             #52            // 同学编号:00
复制代码

运行时常量池

就是在运行时产生的,和静态常量池的区别是产生时间不一样,引用使用的都是直接引用(真实的内存地址)。目的都是为了节约空间避免重复创建。

字符串常量池

JVM实例全局共享的,而运行时常量池和静态常量池的作用域是类。

直接内存

为了避免java堆和Native堆的来回复制,直接使用堆外内存,可以提高效率。比如NIO的DirectByteBuffer引用的是堆外内存。如果空间不足会发生OOM。

使用XX:MaxDirectMemorySize可以进行设置。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享