jvm学习 java内存区域

系统性学习请点击jvm学习目录

JAVA内存区域

java内存区域和C++的内存区域不太相同,之前看一些java教学视频,讲课的老师有时会把C++中的内存划分直接拿过来,栈呀堆呀直接就用上了,但这是不适用于java的,有相似,但是并不相同。java的内存区域划分如下图所示。
在这里插入图片描述
下面,我们就一个一个来看。

程序计数器

接触过微机原理或者计算机组成原理的南波兔们应该对程序计数器不陌生(我们学过计算机组成原理,当初学的是微处理器与嵌入式设计)。废话不多说,直接简介明了的介绍它。

  • 作用:记录当前线程当前字节码执行到哪一条指令了。
  • 特点:线程私有,没有内存溢出风险

程序计数器实际上是一个行号指示器,其中存的是我下一条将要执行的指令。

Code:
       0: new           #2                  // class Test$Father
       3: dup
       4: invokespecial #3                  // Method Test$Father."<init>":()V
       7: astore_1
       8: new           #4                  // class Test$Son
      11: dup
      12: invokespecial #5                  // Method Test$Son."<init>":()V

比如这样的字节码指令,假设我们当前jvm执行到了第三行指令,那么此时程序计数器里存放的就是4,代表下一条要执行第四行指令。
为什么要记录执行到哪了呢?这是因为java虚拟机多线程的特性,我们的cpu可能在多个线程之间来回切换,当某线程重新占用了cpu,怎么才能接上之前中断的地方继续往下执行呢?这个时候程序计数器就派上用场了,程序计数器能告诉cpu我上次的工作停在什么地方了,咱们该从什么地方继续往下走。
也是因为上一小段讲到java虚拟机多线程的特性的缘故,所以我们要为每个线程都配备一个程序计数器,从而导致了程序计数器是线程私有的。
同时,我们的程序计数器只是存储当前执行的指令位置,所以也并不存在内存溢出的风险。

JAVA虚拟机栈

栈就不必多说,数据结构中学过,是一种先进后出的数据结构,我们这里的JAVA虚拟栈,其实你可以看做JAVA方法栈(与本地方法栈相对应,只是存储的是JAVA方法)。

  • 作用:描述的是java方法的执行过程,永远是栈顶的方法被虚拟机执行。
  • 特点:线程私有,声明周期与线程相同,先入后出。

具体来讲一下这个虚拟机栈。
实际上我们一个java程序的执行,不就是许多个方法执行(相互调用)的过程嘛,比如这样一个代码:

public class test {
    public void A(){
        System.out.println("A");
    }
    public void B(){
        System.out.println("B");
    }

    public static void main(String[] args) {
        test ts = new test();
        ts.A();
        ts.B();
    }
}

很简单的代码,我们人工分析一下代码执行的逻辑。首先程序入口是main方法,生成一个test实例,然后执行A方法,在A方法中打印A字符串,然后回到main方法继续执行,然后再执行B方法,在B方法中打印B字符串,然后再回到main方法,发现代码执行完毕,所有代码执行完毕。
而这个逻辑在jvm中就是以java虚拟机栈来实现的。
首先程序入口是main方法,则main方法入栈(一开始栈为空,所以此时main方法是栈顶),然后执行main方法,之后发现其中用到了A方法,那么此时就将A方法入栈,此时A方法是栈顶,执行栈顶的A方法,当A方法执行完毕之后,A方法出栈,此时栈顶的方法又称为了main方法,所以继续执行main方法,之后发现又用到了B方法,那么B方法入栈,B方法称为栈顶,执行B方法,执行完毕后B方法出栈,然后继续执行main方法,当main方法执行完毕,main方法出栈,此时栈空,程序执行完毕。
当每个方法入栈时,jvm会为其创建一个栈帧(老是会觉得是战争,好出戏哈哈哈),栈帧是用来存储局部变量表,操作数栈,动态链接,方法出口灯信息。也就是方法执行时需要的信息。局部变量表会存储当前方法中使用或生成的局部变量,而操作数栈是和jvm的指令挂钩的,jvm的指令是不含操作数的,它都是以操作数栈为核心来执行指令的,这里不细说,后面会专门的来写,动态连接与静态链接相对应,这里不细说,后面会写,暂时只需要知道概念就行。总之,这个栈帧会包含该方法执行时需要的信息。而栈帧的大小,在编译期生成字节码文件就可以确定了。这里不细说,可以见后面写关于字节码的博客。
用一句话来总结,每一个方法被调用直至执行完毕的过程,就对应着一个栈帧(或理解成方法)在java虚拟机栈中从入栈到出栈的过程

本地方法栈

与我们的java虚拟机栈十分类似,只是java虚拟机栈是相对于我们jvm执行的java方法,而本地方法栈是相对于本地方法。这里的本地方法native function,可以看做是属于操作系统的方法,多是用C/C++编写。比如java没法直接操控IO,它需要借助操作系统才能进行IO操作,这里就是用java的IO方法调用操作系统的本地方法,才能完成IO操作,这其中还要涉及到用户态到核心态的转换。

  • 作用:存放对象实例和数组,几乎所有
  • 特点:对象实例,垃圾收集,线程共享,

堆我们在数据结构中也接触过,这里我们抛去那里的概念。
java内存区域中的堆的唯一目的就是用来存放对象实例或数组,我们写的new一个对象,那个对象实例就是放在这里。
堆对于多个线程都是共享的,不过会在其中给每个线程分配一个缓冲区,用以进行对象的分配,从而避免多个线程对同一块堆内存操作,从而导致多线程安全问题。当缓冲区快用完时,才会重新分配缓冲区。
按照jvm标准,java堆可以处于物理上不连续的内存空间,但在逻辑上是连续的。所以堆其实是一个偏向于软件层面的定义。
在谈及堆时,就要提到垃圾收集,jvm的垃圾收集就是针对堆来进行的。这里因为不同的垃圾收集器会将堆划分成不同的区域,比如新生代,老年代,Eden等等,这些都是逻辑上的定义,实际上堆内存还是内存空间,这里具体的我会在垃圾收集器部分的博客介绍。

方法区

  • 作用:存放类的信息(字段,方法,构造器等等),类加载器等
  • 特点:线程共享,与堆区分开

我们编写好代码后,用编译器来编译java代码,便会生成字节码文件,字节码文件中就有了类的描述信息,比如版本,类名,字段,方法等等。然而这些东西都只是在字节码文件中,当我们在运行时,jvm会将字节码文件中的信息加载到内存中,也就是把这些信息放到我们的方法区里来。
在jdk1.8以前,方法区是放在堆中的,是堆的一部分,它是用永久代来实现的(相对于新生代和老年代,不用理解),而jdk1.8之后,方法区改用元空间来实现,不再用堆内存实现,而是用操作系统的内存来实现。
在讲到方法区时,运行时常量池是一定要知道的。运行时常量池与字节码文件中的常量池相关,字节码文件中的常量池的位置位于主次版本号之后的位置,是比较靠前的,其中主要存储的是字面量和符号引用。(字面量可以理解成字符串,比如我们的类名是啥,不就是一堆字符串嘛,而符号引用,就是用符号来代替真实的方法,因为字节码文件只是在编译阶段,并没用进入内存,所以需要调用的方法没法指明其内存地址,所以此时用一个符号引用来代替能指明内存地址的直接引用,来说清楚我们的方法是什么方法,怎么找到它。打个比方,我要找一个人,但是他并没有进内存,也就是没有定居下来,那么我用他的身份证号来标识他,身份证号就是符号引用,等他定居下有了住处,这个住处就是直接引用,可以直接找到他。总之,常量池中存储的就是一些常量还有一些信息,具体后面会有博客来介绍)当类加载进内存时,会把字节码文件中常量池的内容放入到我们java内存区域中的运行时常量池中。

直接内存

  • 作用:用于IO缓存等
  • 特点:属于操作系统,不属于java,但java可以访问

直接内存?这个东西在上面的图中没有呀。对的,就是没有,实际上,直接内存并不存在于java内存区域之中,它是属于操作系统的内存,且它能被jvm访问,但不能管理。
我们在上面也说过java没法直接执行IO操作,它得依赖于操作系统的本地方法。我们一般读取文件进内存,都是先操作系统读取,然后存入自己的缓存,然后把数据传给java内存中的缓存,最后再到java内存中,整个流程经过两个缓存,一个是操作系统的,一个是java的。
在jdk1.4之后新加入了NIO类,引入了一种基于通道和缓冲区的IO方式,简单来说就是本来是两个缓存,现在变成了一个缓存。操作系统从硬盘读取文件,放进自己的缓存,然后java直接读取操作系统的缓存,将文件读入。整个过程就少了一个缓存步骤,大大节省了空间,提高了效率。而这个缓存空间,就是我们的直接内存。

总结

了解java内存区域可以说是对jvm学习的基础之基础把,只有熟悉内存区域,才能进一步学习垃圾回收机制、字节码文件、类加载等等。其中涉及到了一些非本节的知识,我基本都有简单介绍,但之后会继续写博客来介绍。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值