JVM内存管理深度剖析

好久没有写文章了,感觉一年又快过去了。外面天气比较冷,这个时候适合安静的整理一下自己的知识体系了。准备从Java开始深入,到Android高级,再到NDK开始梳理自己这些年的知识脉络,也希望能给大家一点帮助。

1.JVM与操作系统的关系

我们先来看一张图:

 我们知道,一开始我们写一个helloWorld,会有一个helloWorld.java文件,这样的文件我们称之为.java文件,但是.java文件是不可以直接运行在我们的操作系统之上的,也就是说.java文件无法直接调用操作系统的函数。.java文件通过编译器编译成.class文件,.class文件再翻译成机器码(010101...),这样就可以调用操作系统的函数了。所以,JVM可以充当一个翻译的作用,让机器读懂java程序。在oracle官网上,针对不同的操作系统,提供了不同的jdk可供下载,这样就使得在不同的操作系统上,同样的.java文件可以被编译成同样的.class文件,以及同样的机器码,这也就使得java具有跨平台的特性。

最新有一门语言在Android上已经逐渐作为主流语言,对,就是kotlin,kotlin也可以被编译成.class文件,所以JVM又有了跨语言的特性。

2.JVM,JRE,JDK之间的关系

试想如果有一天你去面试,面试官问你,这三者之间有什么关系?相信很多老开发都说不清楚。

我们来看一张图:

 JDK可以理解为java工具包,包括了JRE(java运行时环境),同时JRE又包含了JVM。

我们知道.java文件到.class文件是一个编译过程,需要同javac。而javac就是JDK提供的。

3.JVM内存模型

工作多年的老开发或多或少看过下面这张图:

可以这么说,这张图就是Java内存模型。

下面我们来逐一破解java内存模型:

【1】程序计数器:

首先抛出一个问题:什么是程序计数器?

看上图你只能知道它是JVM运行时数据区的一部分,然而这个答案太肤浅,有失斯文。

我想各位大概率也不是很清楚,还是上图吧。

 上图的0,1,2...看到没有,这些数字就可以理解为程序计数器。作用是记录当前执行字节码的地址。

引申出一个问题:为什么需要程序计数器?

简而言之,就是CPU的时间片轮转机制。我们知道,并发的时候,当前线程可能会被切出时间片,那么就必须有一个计数机制记录当前线程执行到哪一行代码了。其实这一点也很好理解。

程序计数器同时也是JVM唯一一块不会OOM的区域。那么问题来了,为什么程序计数器不会引发OOM血案?程序计数器是记录当前线程字节码执行位置的地址,只需要很小一块位置记录地址就可以了,所以不会引发OOM。

【2】虚拟机栈:

栈的数据结构我在这里不想多说了,简单来说,就是一种先进后出的数据结构。

虚拟机栈这个东西你简单的理解,就是执行方法时方法入栈,一个方法就是一个栈帧。

还是来看一张图吧:

 什么局部变量表啊,什么操作数栈啊,什么返回地址啊,可能越看越迷糊。下面就通过一个方法的压栈实例来详细说明一下个中缘由。

我们来看一段代码:

 

 首先main方法入栈,后面会调用work方法,work入栈。iconst_1意味着将int类型的1入操作数栈,istore_1意思是将操作数栈中的1存入局部变量表,这两步就完成了int x = 1这一行代码操作。iconst_2和istore_2同理完成int y = 2操作。iload_1再次将局部变量表下标为1的int类型数据入操作数栈,iload_2将局部变量表下标为2的int类型数据入操作数栈。然后再出栈进行iadd相加操作,再入栈,再存入局部变量表。iload_3代表将局部变量表中下标为3的变量入操作数栈。return操作再把操作数栈的地址返回。

【3】本地方法栈:

比如我们调用a.hashcode()这个方法的时候,很明显hashcode方法不是一个java方法,它是一个native方法。

我们讲完已经讲完了线程独占区,下面来看看线程共享区:

【4】方法区:

它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

【5】堆区:

Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的

唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

提出一个小问题:为什么方法区和堆区都是线程共享的,不能设计成一个?

其实也很简单,堆区的对象是会频繁创建和回收的,而方法区并不需要,这也是一种动静分离的思想。

JDK1.7之前和JDK1.8以后方法区其实是由区别的,JDK1.7以及之前,方法区是在堆里面的永久代,GC不会在程序运行期间对永久代进行清理,这样就有一个问题,永久代的内存会随着加载class文件的增加而增加,在加载class文件过多时会OOM。

JDK1.8引出了元空间,方法区内存不再使用JVM内存,而使用本机内存。这样做也有一个缺点,就是可能会挤压堆内存。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>