jvm 内存模型

jvm内存模型就是jvm启动的时候从操作系统中要了一块大的内存,然后将其分成了五个部分,方法区、堆、虚拟机栈、本地方法栈、程序计数器;其中堆和方法区是线程共有的,虚拟机栈、本地方法栈、程序计数器是线程私有的。
在这里插入图片描述

1 方法区

方法区是所有线程共享的,方法区是规范,1.8以前方法区的实现是永久代,1.8及以后方法区的实现是元空间;1.8以前永久代主要存储类的元信息、常量池、静态变量;1.8以后常量池和静态变量放到了堆里,用元空间替换了永久代,将类的元信息放到元空间,元空间使用本地内存,所以现在方法区可以使用更多空间,在一定程度上解决了运行时生成大量类造成经常Full GC 问题,如运行时反射、代理。

1.1 为什么移除永久代

1.由于永久代经常不够用或发生内存泄漏,爆出异常:java.lang.OutOfMemoryError: PermGen
    字符串存在永久代中,容易出现性能问题和内存异出。
    类及方法的大小等比较难确定大小,因此对永久代大小指定比较困难,太小容易永久代溢出,太大容易导致老年代溢出。
2.永久代会为GC带来不必要的复杂度,要判断是否是元信息,而且回收效率低。
3.官网说明为融合HotSpot VM 与 JRockit VM 而做出的努力,因为JRockit没有永久代。

1.2 元空间

元空间是使用本地内存,元空间大小仅受本地内存限制。可以通过以下参数来指定元空间大小:参考:https://www.jianshu.com/p/a6f19189ec62
-XX:MetaspaceSize
初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值
-XX:MaxMetaspaceSize
由于metaspace大部分在本地内存中分配,默认基本是无穷大,但仍然受本地内存大小的限制。为了防止metaspace被无止境使用,建议设置这个参数。
-XX:CompressedClassSpaceSize
压缩类空间大小,默认1G,如果设置了-XX:-UseCompressedClassPointers,或者-Xmx设置大于32G,则这个参数不生效。
-XX:MinMetaspaceExpansion
增大促发Metaspace GC阈值的最小要求。
-XX:MaxMetaspaceExpansion
增大触发metaspace GC阈值的最大要求
-XX:MinMetaspaceFreeRatio
默认40,表示每次GC完之后,如果metaspace内存的空闲比例小于MinMetaspaceFreeRatio%,那么将尝试做扩容,增大触发metaspaceGC的阈值。不过这个增量至少是MinMetaspaceExpansion才会做,不然不会增加这个阈值。
-XX:MaxMetaspaceFreeRatio
默认70,这个参数和上面的参数基本是相反的,是为了避免触发metaspaceGC的阈值过大,而想对这个值进行缩小。

查看元空间命令:
java -XX:+PrintFlagsFinal -version | grep Metaspace
由于调整元空间会促发Full GC 这是非常昂贵的操作,所以如果在启动时发生大量Full GC,通常都是元空间发生了大小调整,所以建议将MetaspaceSize和MaxMetaspaceSize设置为一样大,建议调成物理内存的1/32。MetaspaceSize是元空间首次不够促发Full GC的阈值。

2 堆

结构如下:
在这里插入图片描述
堆是虚拟机管理的内存中最大的一块区域,是所有线程共享的区域,也是垃圾回收的重点区域,该区域存放了对象实例和数组;堆分为了新生代和老年代,比例为1:2,新生代又分为了Eden区、S0区、S1区,比例为8:1:1。
可以通过如下参数调节堆的大小:
-Xms
最小值,默认为物理内存的1/64
-Xmx
最大值,默认为物理内存的1/4
-XX:MinHeapFreeRation
设置空余堆小于多少时增调大内存,默认40%
-XX:MaxHeapFreeRation
设置空余堆大于多少时调小内存70%
-XX:NewRatio
设置年轻代与老年代的比例
-Xmn
设置新生代大小
-XX:SurvivorRatio
设置伊甸区和幸存区的比例

当空余堆内存小于MinHeapFreeRation时,jvm会增大堆内存到-Xmx指定大小,当空余堆内存大于MaxHeapFreeRation时,JVM会减小堆内存的大小到-Xms指定的大小,为了避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。

2.1 什么样的对象进入老年代

1.15次GC后还存活的对象。

2.大对象,指大小超过Eden区一半的对象。

3.空间担保,指发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么这一次Minor GC可以确保是安全的,如果不成立,则虚拟机会先查看-XX:HandlePromotionFailure参数的设置值是否允许担保失败;如果允许,那么会继续检查老年代的最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将会尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者-XX:HandlePromotionFailure设置不允许冒险,那么这时就要改为进行一次Full GC。
在这里插入图片描述

4.动态年龄判断,指发生minor gc后如果存放对象的那块Survivor空间中低于或等于某年龄(比如年龄为n)的所有对象大小的总和大于Survivor空间的50%(通过-XX:TargetSurvivorRatio 设置),那么年龄大于或等于该年龄(即n)的对象就可以直接进入老年代,不需要年龄到达15。

3 虚拟机栈

虚拟机栈是描述java方法执行的内存模型,虚拟机栈中存放的是栈帧,每一个方法开始执行都会创建一个栈帧,用于存放方法的局部变量表、操作数栈、动态里链接、返回地址、附加信息,每一个方法开始执行到结束都对应着一个栈帧的入栈和出栈。

3.1 栈帧

在这里插入图片描述

局部变量表

存放方法的参数和方法内部定义局部变量,在编译期间就确定了局部变量表的容量。非静态方法的局部变量表的第一个变量是this。

操作数栈

存放操作数的栈,虚拟机把操作数栈作为其工作区域,大多数指令都要从这里弹出数据,执行运算,然后将结果压会栈中。
例如下面代码:

int a = 1;
int b = 2;
int c = a+b;

对应字节码指令:

0 iconst_1        // 将int 1压入操作数栈
1 istore_1		  // 将操作数栈顶int类型保存到局部变量表1
2 iconst_2		  // 将int 2压入操作数栈
3 istore_2		  // 将操作数栈顶int类型保存到局部变量表2
4 iload_1		  // 将局部变量表1的int类型压入操作数栈
5 iload_2		  // 将局部变量表2的int类型压入操作数栈
6 iadd 			  // 将操作数栈顶两int类型相加,将结果压入栈
7 istore_3		  // 将操作数栈顶int类型保存到局部变量表3

动态链接

该方法对应的jvm对象在方法区的内存地址。

返回地址

保存上层方法的局部变量表及操作数栈指针,保存方法调用入口的下一条指令即上层方法的程序计数器。

附加信息

保存java虚拟机实现的一些附加信息,例如堆程序调式提供支持的信息。

3.2 this指针赋值

public class Demo2 {
    public static void main(String[] args) {
        Demo2 demo2 = new Demo2();
    }
}

查看 Demo2 demo2 = new Demo2();的字节码指令

0 new #2 <com/jt/learn/jvmmemory/stack/Demo2>
	// 在堆区生成了一个不完全对象(未执行构造方法)
	// 将不完全对象指针压入栈
3 dup
	// 复制栈顶元素,将复制后的元素压入栈
4 invokespecial #3 <com/jt/learn/jvmmemory/stack/Demo2.<init>>
	// 完成运行方法的环境搭建this指针赋值,即将栈顶元素保存到局部变量表0的位置。非静态方法的局部变量表0位置就是this指针。
	// 执行构造方法,完全对象。
7 astore_1
	// 将栈顶引用类型值保存到局部变量表1的位置

3.3 方法调用过程

public class Demo {
    public static void  test() {
        System.out.println("运行test方法");
    }
    public static void main(String[] args) {
        test();
    }
}

执行test方法的过程:
1.创建一个test方法的栈帧。
2.在test方法的栈中中保存main方法字节码的下一行程序计数器。
3.线程的局部变量表开始指针(main的)保存到test方法的栈帧。
4.线程的操作数栈开始指针(main的)保存到test方法的栈帧。
5.将test方法的局部变量表指针赋值给线程的局部变量指针。
6.将test方法的操作数栈指针赋值给线程的操作舒适栈指针。

4 程序计数器

保存字节码指令的行号。

5 本地方法栈

和虚拟机栈类似,主要为虚拟机用到native方法服务。

参考链接:https://www.jianshu.com/p/0ecf020614cb

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值