JVM内存模型及内存分配

JVM内存模型

首先先让我们看看内存结构图

然后我们来具体介绍下每一部分的内容:

类加载器

之前有一篇文章讲解了有关类加载的机制,在这里就不再赘述了,有需要的朋友请移步查看

虚拟机栈

虚拟机栈描述的是Java方法执行的动态内存模型,虚拟机栈中最重要的就是栈帧的概念

栈帧

每个方法的执行,都会创建一个栈帧,伴随着方法从创建到执行完成,栈帧中有局部变量表、操作数栈、动态链接,方法出口等

局部变量表

存放编译期可知的各种基本数据类型,引用类型,returnAddress类型(仅存在于字节码层面,returnAddress类型的值就是指向特定指令内存地址的指针)
局部变量表的内存空间在编译期分配完成,在进入一个方法时,这个方法需要在栈帧中分配的内存大小是固定的,在方法运行期间不会改变局部变量表的大小,任何会改变大小的对象都会使用引用类型来指定

操作数栈

Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中的栈就是操作数栈,虚拟机把操作数栈作为他的工作区,大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈

动态链接

每一次运行期间,都会转化为字节引用,指向运行时常量池中该栈帧所属方法的引用

方法返回地址

方法执行结束后应该回到哪一行字节码继续执行,该地址的获取有两种方式,一是方法正常退出通过PC计数器获取,另一个则是方法执行发生异常后通过异常处理表获取

本地方法栈

和虚拟机栈类似,最大的不同就是本地方法用于本地方法的调用,即native修饰的方法,JVM允许Java直接调用本地方法(由C语言编写)

程序计数器

程序计数器是一块比较小的内存空间,他可以看作是当前线程所执行的字节码的行号指示器
在任意时刻,一个线程总是在执行一个方法,这个方法称为当前方法,如果线程执行的是Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是native方法,则程序计数器的值为undefined

Java堆

Java堆解决的是数据存储的问题,几乎所有的对象实例都存放在Java堆中
堆内存分为新生代和老年代(Tenured Gen),分配比例默认为1:2
其中新生代又分为Eden和两个Survivor区(From和To),分配的默认比例为8:1:1

方法区

方法区是辅助Java堆的一块内存区域,用于存储虚拟机加载的类信息、常量、运行时常量池、即时编译器(JIT)编译后的代码等数据

JVM中特殊的内存区域

运行时常量池(隶属于方法区)

存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区(共享内存区)的运行时常量池存放,其中的字面量(字符串常量)会被存放到一个字符串表中,该表为HashSet类型,具有无序不可重复的特点,所以相同的字面常量自然就是同一块内存地址,通过new的方式创建的同字面量字符串不会考虑常量池的对象,而是直接在堆中重新开辟一块内存空间,所以必然和常量池对象地址不同

直接内存

在NIO中会使用到直接内存用于提高IO性能,其他地方几乎是不用的,通常直接内存速度会优于java堆,读写频繁的场合可能会考虑使用
直接内存不受制JVM堆内存的制约,但是依旧受限于物理内存的制约,如果内存满了也会抛出OutOfMemoryError异常
不过可以调用String对象的intern()方法实现运行时加入常量池,从而实现System.out.println("str"==new String("str").intern())的输出为true

JVM内存分配

内存分配策略

优先分配到Eden

测试方法:在方法中直接分配一个不超过Eden总内存的大对象,通过打印GC信息来查看是否是在Eden区分配的内存

大对象直接分配到老年代

当新对象的大小超出了Eden的内存上限,将直接在老年代中给该对象分配内存

空间分配担保,默认是开启的,可以通过-XX:-HandlePromotionFailure来关闭

当Eden区的空间已不足以存放新建对象,而且Survivor中空间也不够时(必然不够),只能将原有新生代的内容直接存放到老年代中,从而给新对象腾出足够的空间

动态对象年龄判断

对于在From和To两个Survivor区流转的对象,当流转次数达到某个值时(默认为15),会被存入到老年代中

逃逸分析和栈上分配

栈上分配

Java栈的方法执行完成后会自动释放空间,所以如果将方法中的局部变量直接在栈中进行分配,在方法执行完毕时,对象会跟随方法释放掉,不需要垃圾收集器的介入,可以提高程序效率

逃逸分析

对于一个方法中的变量,分析其作用域,只要能够确定该变量没有方法外的引用,那就说明该变量没有发生逃逸,也就可以使用栈上分配

对象的创建流程

先尝试栈上分配,满足则分配,不满足尝试TLAB(Thread Local Allocation Buffer)分配,满足则分配,不满足查看是否满足进入老年代,满足则在老年区进行分配,不满足则在Eden区进行分配

TLAB,称为本地线程分配缓冲,位于Eden区域中,每个线程都会有的一个独占区域,目的是为了在多线程情况下,避免发生线程安全的问题,当该空间满了之后通过同步的方式增加缓冲区大小

如果对你有帮助,点个赞,或者打个赏吧,嘿嘿
整理不易,请尊重博主的劳动成果

posted @ 2020-04-29 19:11  Mango_SF  阅读(391)  评论(0编辑  收藏  举报