3分钟搞懂jvm内存模型

1. 程序计数寄存器

程序计数寄存器作用是用来记录线程执行到哪了,好下次接着继续执行,有点想游戏存档的感觉。程序是顺序执行的,为什么需要这种存档机制呢?因为程序虽然是顺序执行,但是程序执行的过程中线程会有多条,并且各线程并行执行。

假设我们的计算机只有一个单核的CPU,那么这个CPU要同时处理多个线程。CPU会划分成多个时间片,来回在各个线程之间来回切换。举个例子,CPU先在A线程上处理一个时间片,又在B线程上处理一个时间片,当CPU要返回A线程处理的时候,却不知道对于A线程处理到哪里了。这些尴尬了,CPU不知道怎么继续工作了。

要解决这个问题,jvm在内存里面找了一小块区域,来存放当前各个线程所执行到的位置。做个实验,

先写段helloworld:

public class Hello {
    public static void main(String[] a) {
        System.out.println("Hello world");
    }

}

再javac编译以下,生成了Hello.class文件。

再使用javap -c Hello.class:

字节码中第一列的数字便是程序计数寄存器记录的指令地址,第二列是执行的指令。

jvm会在程序计数寄存器中记录的指令地址,找到指令,从而执行指令。综上,程序计数寄存器是为了程序多线程执行所设定的,它不是CPU上的寄存器,是逻辑上的寄存器,是逻辑上在内存上开辟的一小块空间,是jvm中唯一一块没有outOfMemoryException的逻辑块。

2. 虚拟机栈

虚拟机栈处于内存,每个单元叫做栈帧。栈帧内分成两个区,一个是操作数区,还有一个是局部变量区。好了,现在有三个名词:栈帧、操作数区、局部变量区。首先要明白的是,虚拟机栈是放java方法的地方,它的所有设计都是与java方法息息相关的。明白了这个,思考一个问题,为什么虚拟机栈这种数据结构要设计成栈的方式?

写过代码的同学都知道,java方法从外层方法开始执行,一个一个方法的向内调用直到最内层的方法。最内层的方法再将结果一层一层的返回值最外层方法。瞧瞧这个过程,最外层方法是最先执行的,然而却是最后一个执行完毕的。再瞧瞧栈这种数据结构的特点,先进后出,这种特点是不是正好跟java方法的执行方式相吻合?好了,现在同学们应该明白为什么要使用栈这种数据结构了吧。

再说那三个名词。首先时栈帧,一个栈帧放着一个java方法,换句说法java方法要被调用的时候,在jvm里面是以栈帧为单位将方法压进栈的。在每个栈帧里,jvm把操作数都放在操作数区,把局部变量都放在局部变量区。听起来是不是有点像汇编?jvm面向的是字节码文件,确实很像汇编。回归正题,操作数区实际上也是一个栈,对于操作数的操作就是进栈出栈的操作。比如,1+2。首先1进栈,再2进栈,再栈顶两个元素相加返回结果出栈。

虚拟机栈有两种异常类型:StackOverflowError和OutOfMemoryError。

首先看StackOverflowError,这种异常表示方法调用的层数太多了,超过了设定的上限。比如,如果大家运行以下代码,那么就会报出这错误。

package aaa;

public class A {

    public static void main(String[] a) {
        new A().funcA();
    }

    public void funcA() {
        funcB();
    }
    public void funcB() {
        funcA();
    }

}

再看OutOfMemoryError,表示超出了jvm分配的内存大小。

最后,虚拟机栈是线程私有的。

3. 虚拟机堆

关于虚拟机堆,有几个点:

(1)首先,虚拟机堆是存放对象实例的地方。说简单一点,就是new出来的东西会放在堆里。

举个例子:

String str1 = new String("aaa"); System.out.println(str1 == "aaa");

结果会输出false。原因是,str1放在了虚拟机栈中,“aaa”作为对象实体,被放在虚拟机堆中,“==”表示对比地址,所以返回false。

再看,String str2 =“bbb”; System.out.println("str2 == "bbb");

结果会输出true。原因是,str2在虚拟机栈中,“bbb”作为基础数据类型也放在虚拟机栈中,两者地址相等,所以返回true。

(2)虚拟机堆是多线程共享的,是一块蛮大的内存空间。虚拟机栈在物理空间上并不要求连续,它只要求在逻辑上是连续的。jvm垃圾收集机制,会回收没用的对象实体,释放内存空间。

(3)虚拟机堆的常见报错是OutOfMemoryError。

看下怎么人造报错:

public class A {

    public static void main(String[] a) {
        List<String> arrayList = new ArrayList<String>();
        while(true) {
            arrayList.add("aaaaaaaa");
        }
    }

}

4. 虚拟机方法区

方法区也是多线程共享的,用来存储系统的类信息,包括字段、方法、常量、静态变量。

这个区域也有OutOfMemoryError报错。系统里面的类太多了,就会包oom的错误。在java8中可以用-XX:MaxMetaspaceSize来确定方法区的大小。像制造这种错误,可以将方法区设置的小一点,然后多造些静态变量,多引入一些类,就剋以造出该错误了。

该区域缺乏垃圾回收的管理,一般不回收,这个搞的不太好。总之,方法区是保存一些静态的、不经常改变的东西。

 

最后,用个小例子总结下,jvm内存模型。

看这句话:String a = new String("aaa");

首先,程序开始执行,执行到这个语句,jvm会将该指令的地址放到程序计数器中。String这种类的相关信息和jit编译出来的指令会被放入方法区,因为String类的这些信息都是不怎么变化的,是静态的。然后,变量a会被压入虚拟机栈的常量区,最后,new出的实体“aaa”会被放到虚拟机栈中。执行过程,可以参考下我的另一篇文章:https://blog.csdn.net/qilinxo/article/details/85045341

没了。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值