【JVM系列】JVM内存区域(JVM运行时数据区域)

有志同道合的朋友可私信联系我,这些年攒下来一些的技术文档可免费分享

一、基础知识

JVM是什么?

  JVM是一种抽象的规范,规范我们的程序开发;JVM也是一个具体实现,即我们平常理解的一个软件;JVM还可以说是运行的虚拟机实例,即加载class文件并执行它里面字节码的服务。


JVM、JRE、JDK 的关系

  JVM(Java Virtual Machine) 的作用是把 Class 翻译成机器识别的代码,我们平时在写代码时会依赖很类库,包括运行JVM时都需要依赖一些类库,这时就需要用到JRE。

  JRE(Java Runtime Environment)提供了很多的基础类库(即jar 包)。

  JDK(Java Development Kit),从名称上就能看出是Java开发工具,所以JDK提供了很多帮助我们开发的工具包,比如 javac(编译代码)、java、jar (打包代码)、javap(反编译<反汇编>)等。


Java 程序的执行过程

  Java 文件 -> 编译器 -> 字节码 -> JVM -> 机器码。

  Java 程序,首先经过 javac 编译成 .class 文件,然后 JVM 将其加载到方法区,执行引擎将会执行这些字节码。


二、运行时数据区

内存划分

在这里插入图片描述
  方法区(永久代)在1.8之后叫元空间了,使用的是本地内存

  hostspot已经将本地方法栈(执行native方法)和虚拟机栈合二为一了

  堆和方法区是线程共享区域,虚拟机栈和程序计数器是线程私有的。

  下面是一个比较细节的划分图:
在这里插入图片描述


  堆用来存放对象和数组,只要是堆中的对象,就可以被所有线程共享(静态变量、静态常量、字符串存储在堆中的老年代里)。Java7 版本中将永久代的静态变量运行时常量池转移到堆中存放的。

  堆是 JVM 上最大的内存区域。垃圾回收操作的对象就是堆。

  堆空间一般是程序启动时就申请了,一般设置成可伸缩的。 随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收,这就是GC。

   对于基本数据类型对象(如byte、short、int、long、float、double、char),在方法体内声明时,会直接分配在栈中,其它情况都会分配在堆中。

  对于普通对象来说,JVM 会首先在堆上创建对象,然后在其他地方使用它的引用。比如,把这个引用保存在虚拟机栈的局部变量表中。但是在开启了逃逸分析时,如果发现某个对象只会在方法内部使用,则可能会将该对象经过标量替换后也存在栈中。

堆的几个重要参数:
  -Xms:堆的最小值(初始值,默认单位是:字节,要求是1024的整数倍);
  -Xmx:堆的最大值;
  -Xmn:新生代的大小;
  -XX:NewSize;新生代最小值(初始值);
  -XX:MaxNewSize:新生代最大值;


虚拟机栈

  Java虚拟机栈是当前线程在执行方法时存储所需的数据、指令、返回地址的一种栈结构(先进后出)。它的生命周期与线程保持一致。提一句:静态变量不入栈

  每调用一个方法就会在栈里加入一个栈帧。调用的方法执行完了,对应的栈帧就会出栈。栈帧里分为4个区域,这4个区域就包含了执行Java方法时的全部内容。这个4个区域分别是:局部变量表、操作数栈、动态连接、方法出口
在这里插入图片描述
  虚拟机栈默认1M。 如果我们不断的往虚拟机栈中入栈帧,但是就是不出栈的话,那么这个虚拟机栈就会溢出。

  上面图示中,我只画了方法2对应栈帧里的4个区域,省略了方法1的4个区域。下面挨个解释栈帧里的这4个区域。

1.局部变量表
  存方法参数和局部变量。如果是引用类型则存的是对象在堆中的地址。第0行,放的就是this。

2.操作数栈
  操作数栈也是一个先进后出的栈结构,它用来临时存放即将要操作的数据。通过下面代码的执行过程,可一探操作栈的具体作用:

public int work() {
    // 将1压入操作数栈,再从操作数栈移到 局部变量表中
    int x = 1;
    // 将3压入操作数栈,再从操作数栈移到 局部变量表中
    int y = 3;
    // 先从局部变量表将x 的值压入操作数栈,再将y 的值压入操作数栈
    // 然后按照先进后出的原则将两个数弹出移到执行引擎相加
    // 加完结果存入操作数栈,再将10压入操作数栈
    // 然后按照先进后出的原则将两个数弹出移到执行引擎相乘
    // 接着再将相乘结果压入操作数栈,最后再弹出移到局部变量表
    int z = (x + y) * 10;
    // 将3从局部变量表拿到操作数栈,然后弹出返回
    return z;
}

3.动态连接
  Java 语言特性多态(需要类加载、运行时才能确定具体的方法),如子类重写父类方法后,具体调用哪个要等到执行时才能确定。这时候的符号引用转化为直接引用称为动态链接

4.方法出口
  方法出口其实就是记录的返回地址。例如:方法1调用了方法2之后,那么方法2的出口地址就是方法1调用方法2的代码位置,因为在方法2执行完了之后,要回到方法1继续执行。
  正常返回(调用程序计数器中的地址作为返回)
  异常返回(通过异常处理器表<非栈帧中的>来确定)


程序计数器

  由于现在都是多线程运行,而一个CPU在同一时刻只能运行一个线程,多个线程只能交替运行。程序计数器的作用就是记录当前线程下一条要运行的指令,这样保证了线程在切换回来时能回到正确的位置继续开始执行

  程序计算器是唯一不会发生内存溢出的地方。如果正在执行的是Native 方法,由于不是JVM执行,则这个计数器值为空(Undefined)


方法区(元空间)

  方法区也是一个线程共享的内存区。

  方法区存储的内容有:类型信息(比如类全称、父类全称)、域信息(域名称、域修饰符private等)、方法信息(方法名称、方法修饰符、返回类型等)、字面量(字面量包括文本字符串、八种基本类型的值 、被声明为final的常量等)。

  假如两个线程都试图访问方法区中的同一个类信息,而这个类还没有加载进 JVM,那么此时就只允许一个线程去加载它,另一个线程必须等待。

  方法区是 JVM 对内存的“逻辑划分”,在 JDK1.7 及之前将方法区称为“永久代”,是因为在 HotSpot 虚拟机中,设计人员使用了永久代来实现了 JVM 规范的方法区。在 JDK1.8 及以后使用了元空间来实现方法区。

  Java8 使用元空间替代永久代,是为了融合 HotSpot JVM 与 JRockit VM ,因为 JRockit 没有永久代。

  元空间大小参数设置:
  jdk1.7 及以前:-XX:PermSize;-XX:MaxPermSize;
  jdk1.8 以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize ;如果不设置参数,则只受本机总内存的限制

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值