前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >要想精通java,你必须得知道java的内存模型,不忽悠

要想精通java,你必须得知道java的内存模型,不忽悠

作者头像
架构师修炼
发布2020-07-20 10:21:40
4730
发布2020-07-20 10:21:40
举报
文章被收录于专栏:架构师修炼架构师修炼

我们在写一个java程序的时候,然后将其编译成class字节码,最后将字节码放到Java虚拟机(JVM)中运行。也就是是它是java运行的载体,可见这个JVM有多重要。

我们大部分java程序员喜欢java语言的一个重要原因就是因为JVM它有一套自己的内存管理机制,不需要我们开发人员去关注每个对象的内存分配和回收,将更多的精力花在业务本身上。还有就是我们在面试的时候,也会经常被问到Java虚拟机的内存管理模型等。那么,我们今天就来学习下JVM的内存模型

01

为什么要学习JVM内存模型

我们知道在开发中我们很少关注程序对象的内存管理,只是一味的去写程序然后运行程序,对于内存的分配和回收都是依靠默认的JVM去帮我们管理,正是因为这样,我们有时候运行中的程序经常会出现内存溢出或者GC频繁造成的系统卡死等情况。如果,我们能更好的掌握JVM的这些策略,了解它的内存区域划分的话,我们就会很好的去避免故障的频繁发生,同时还可以针对我们的系统对其进行相应的优化。所以,为了我们程序能够很好的去运行,也为了我们自己少加班,我们第一步需要知道Java虚拟机的内存模型

02

JVM虚拟机划分

JVM总共有5大内存区域划分,分别是堆、方法区、程序计数器、虚拟机栈、本地方法栈。先来看下内存模型图

你肯定疑问为什么要搞这几个区域呢,直接丢到jvm的内存去不就行吗?其实不然,我们写的java程序,是需要多块内存空间的,不同的内存空间用来放不同的数据,然后再加上我们代码中写的具体的逻辑,这样才能让我们开发的系统运行起来。也就是说,你知道咱们写的java类被加载到JVM哪里吗,写的那些方法又存在哪吗,还有方法里面的那些变量也需要内存去存放吧。

这就是为什么JVM中必须划分出那些不同的内存区域,它也就是为了我们写好的代码在运行过程中根据需要来使用的,那下面我们就来看看这些区域各是存些什么东西的。

1

方法区

在JDK1.8之前,方法区经常被称作是永久代,是JVM中的一块内存区域,主要是放我们.class文件里加载进来的类,同时还有一些类似常量池的东西也放在这个区域。

但是在JDK1.8后,这块区域就改了名,叫元空间“MetaSpace”,主要也就是存放我们写的各种java类的相关信息。比如,我写了下面这样的代码,有两个类“MyTest.clss”和“Study.class”。

public class MyTest {

public static void main(String[] args) {

Study study = new Study();

}

}

那么,这两个类的信息就会被加载到JVM的方法区里来。

2

程序计数器

我们写的java程序会被编译成字节码,字节码对应着各种字节码指令,这样我们的计算机才认识的出来。所以当JVM加载信息到内存之后,实际就会使用自己的字节码执行引擎,去执行我们编译出来的那些代码指令,那么在执行字节码指令的时候,JVM里就需要一块很小的内存空间,即为“程序计数器”

为什么需要程序计数器内?因为我们需要有一个东西来记录我们的程序执行到哪了。

我们知道java语言是支持多语言的,所以如果我们开启了多个线程的话,就会有多个线程来执行不同的代码指令,也就是说每个线程都会有自己的程序计数器。

3

java虚拟机栈

每个线程都有自己的虚拟机栈,用来存放它执行的方法里面的局部变量等数据,如果线程执行了一个方法,就会对这个方法调用创建对应的一个栈帧,栈帧就包含了这个方法的局部变量。

public class MyTest {

public static void main(String[] args) {

Study study = new Study();

study.studyJava();

}

}

例如上面main主线程执行了main方法,就会给这个main方法创建一个栈帧,压入main线程的虚拟机栈中,并且在main方法的栈帧里,会存放对应的“study”的局部变量。

接着,假设main线程执行到了Study这个类的studyJava这个方法,studyJava方法里有一个局部变量“language”

public class Study {

public void studyJava() {

String lanuage="java";

}

}

那么main线程就会为stusyJava()方法创建一栈帧压入到自己的虚拟机栈中。如下图:

然后studyJava方法执行完毕之后,就会从java虚拟机栈出栈,直到main方法也出栈。

所以,JVM的虚拟机栈的作用是:线程在调用方法时,都会为其创建栈帧然后入栈,在栈帧里面存放有方法的局部变量以及方法执行的相关信息,等到方法执行完毕就会出栈。

4

堆(Heap)

现在我们已经知道了每个线程有自己的程序计数器以及虚拟机栈,接下来我们再来看另一个重要的区域,java堆,这个区域也是和我们打交道最多的区域。我们new出来的各种对象就会放在这个堆里面。

public class MyTest {

public static void main(String[] args) {

Study study = new Study();

study.studyJava();

}

}

比如,我们现在的Study实例就存在java堆内存里面,当我们执行main方法的时候,在栈帧里面的study局部变量就存放了Study在堆内存的地址。

5

本地方法栈

本地方法栈和java虚拟机栈功能类似,java虚拟机栈用来管理java函数的调用,而本地方法栈是用来管理本地方法的,但是本地方法并不是java语言实现的,而是C语言实现的。比如我们java类库中好多是native的方法。

03

全流程精讲

上面,我们已经知道了各个内存区域应该存什么内容了,下面我们将这些区域整个全流程串起来再看看,我们就会豁然开朗了。

public class MyTest {

public static void main(String[] args) {

Study study = new Study();

study.studyJava();

}

}

结合上面代码和图,我们来看看代码和这个JVM的内存模型的关系:

  • 首先我们的JVM进程启动的时候,就会加载Mytest类到JVM内存中来。
  • 然后就会有一个主线程main来执行我们的main()方法代码,上面我们说到线程执行字节码指令是需要程序计数器记录代码锁执行的位置的。
  • 接着就将main()方法压入本线程的java虚拟机栈中,发现在main()方法new出来个Study实例,这时候就会加载Study实例分配到java堆内存中,并且将堆内存地址放入study变量,在执行studyJava方法,一样的流程
  • 最后,方法执行完之后,就会出java虚拟机栈。

我相信,通过上面的各个区域的模块的讲解加上现在的全流程代码和内存模型对应的讲解,大家肯定都已经很清楚了JVM的内存模型并且知道我们的代码是怎么和其对应的了。

总结,今天将我们的java语言的基石JVM内存模型进行的详细的分析,主要包括方法区、程序计数器、java虚拟机栈、本地方法栈(可以适当了解)java堆,并且也通过代码运行的模式对应JVM的各个内存区域进行了分析,目的就是为了让我们更好的去理解JVM的内存模型。如果大家喜欢,就关注我,我会不停更新我们的各种实战技术,谢谢

下一篇预告:聊聊类是怎么加载的话题

关于架构师修炼

本号旨在分享一线互联网各种技术架构解决方案,分布式以及高并发等相关专题,同时会将作者的学习总结进行整理并分享。

更多技术专题,敬请期待

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构师修炼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档