JVM内存初理解

Posted Java在途中

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM内存初理解相关的知识,希望对你有一定的参考价值。


前沿

为什么有时候学着学着会突然之间觉得一切度是那么无趣,男的每个月也有那么几天难道?哈哈,不然是什么,我还是要坚持,可以做少一点,但是不能什么都不做。总会过去的,加油!

目标

  • JVM介绍

  • JVM内存分析

  • 案例分析

开始

一、JVM介绍

JVMJava Virtual Mechinal(Java虚拟机)。 JRE的一部分,是虚拟的计算机,其拥有完善的硬件架构及指令系统。JVM的主要工作是解释自己的指令集(字节码),将其映射到本地的CPU指令集或OS的系统调用。Java实现跨平台,便是因为不同的操作系统,使用不同的JVM映射规则。

JVM内存初理解

JDK、JRE、JVM关系

二、JVM内存介绍

JVM内存从区域大致可分为堆和栈,具体包括:方法区、堆、虚拟机栈、本地方法栈、程序计数器。

是不是很懵逼,完全不知道它们代表着什么,及存在意义在哪里。

首先,需要了解JVM内存的区域划分,如下图:

JVM内存初理解

程序计数器(寄存器) 

释义:

1.当前线程所执行的字节码行号指示器

2.字节码解释器工作依赖计数器控制完成

3.通过执行线程行号记录,让线程轮流切换各条线程之间计数器互不影响

4.线程私有,生命周期与线程相同,随JVM启动而生,JVM关闭而死

6.线程执行Nativan方法时,计数器记录为空(Undefined)

7.唯一在Java虚拟机规范中没有规定任何OutOfMemoryError情况区域

JVM内存初理解

是不是更懵逼,这都是什么鬼?

这玩意,多线程运行时,当一个线程停止,另一线程运行,当CPU再次回到第一个线程时,其又能从暂停的地方开始运行,那该线程为什么能记住暂停的位置呢,这就要依靠程序计数器了,这下明白了吧。

本地方法栈

本地方法栈与虚拟机栈相似,不过服务于本地方法,有些虚拟机将这两个区域合二为一。

本地方法栈中抛出异常的情况与虚拟机栈相同。

那其作用具体是什么呢,下面我通俗的解释一下:

看过源码的同学都应该知道,很多算法或者功能的实现,都被Java封装到本地方法中,程序直接通过调用本地的方法即可,本地方法栈便是用来存放这种方法的。

下面重点来啦!

虚拟机栈

虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用来存放存储局部变量表、操作数表、动态连接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

这个话怎么理解呢?比如执行一个类(类中有main方法)时,执行到main方法,就会把为main方法创建一个栈帧,然后在加到虚拟机栈中,栈帧中会存放这main方法中的各种局部变量,对象引用等东西。如图:

JVM内存初理解

当在main方法中调用别的方法时,就会有另一个方法的栈帧入虚拟机栈,当该方法调用完了之后,弹栈,然后main方法处于栈顶,就继续执行,直到结束,然后main方法栈帧也弹栈,程序就结束了。总之虚拟机栈中就是有很多个栈帧的入栈出栈,栈帧中存放的都市一些变量名等东西,所以我们平常说栈中存放的是一些局部变量,因为局部变量就是在方法中。也就是在栈帧中,就是这样说过来的。

通常来说,堆是Java虚拟机管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建,堆的作用就是存储对象实例。

几乎所有的对象实例度在这里分配内存,也就是通常我们说的new对象,该对象就会在堆中开辟一块内存来存放对象中的一些信息,比如属性。

同时堆也是垃圾收集器管理的主要区域。因此很多时候被称为"GC堆",虚拟机的垃圾回收机制等下一篇文章来讲解。 在上一点讲的栈中存放的局部引用变量所指向的大多数度会在堆中存放。

方法区和其中的运行时常量池

和堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、和编译器编译后的代码(也就是存储字节码文件.class)等数据。

这里可以看到常量也会在方法区中,是因为方法区中有一个运行时常量池,为什么叫运行时常量池,因为在编译后期生成的是各种字面量(字面量的意思就是值,比如int i=3,这个3就是字面量的意思)和符号引用,这些是存放在一个叫做常量池(这个常量池是在字节码文件中)的地方,当类加载进入方法区时,就会把该常量池中的内容放入运行时常量池中。

这里要注意,运行时常量池和常量池,不要搞混淆了,字节码文件中也有常量池,在后面的章节会详细讲解这个东西。现在只需要知道方法区中有一个运行时常量池,就是用来存放常量的。

还有一点,运行时常量池不一定就一定要从字节码常量池中拿取常量,可能在程序运行期间将新的常量放入池中,比如String.intern()方法,这个方法的作用就是:先从方法区的运行时常量池中查找看是否有该值,如果有,则返回该值的引用,如果没有,那么就会将该值加入运行时常量池中。

补充

常量:用final修饰的成员变量表示常量,值一旦给定就无法改变!

final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。


Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。


静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。


运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

三、案例分析

平常分析中用到的最多还是堆、虚拟机栈和方法区。


例如:看下面这段程序,然后画出内存分析图。

JVM内存初理解

1、首先运行程序,Demo1_car.java就会变为Demo1_car.class,将Demo1_car.class加入方法区,检查是否字节码文件常量池中是否有常量值,如果有,那么就加入运行时常量池


2、遇到main方法,创建一个栈帧,入虚拟机栈,然后开始运行main方法中的程序



4、然后通过c1这个引用变量去设置color和num的值,


5、调用run方法,然后会创建一个栈帧,用来装run方法中的局部变量的,入虚拟机栈,run方法中就打印了一句话,结束之后,该栈帧出虚拟机栈。又只剩下main方法这个栈帧了


6、接着又创建了一个Car对象,所以又在堆中开辟了一块内存,之后就是跟之前的步骤一样了。 

补充

1、创建对象,在堆中开辟内存时是如何分配内存的?


两种方式:指针碰撞和空闲列表。我们具体使用的哪一种,就要看我们虚拟机中使用的是什么了。


指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存度放一边,空闲的内存放另一边,中间放着一个指针作为分界点的指示器,所分配内存就仅仅是把哪个指针向空闲空间那边挪动一段与对象大小相等的实例,这种分配方案就叫指针碰撞。


空闲列表:有一个列表,其中记录中哪些内存块有用,在分配的时候从列表中找到一块足够大的空间划分给对象实例,然后更新列表中的记录。这就叫做空闲列表


2、对象引用是如何找到我们在堆中的对象实例的?     


这个问题也可以称为对象的访问定位问题,也有两种方式。句柄访问和直接指针访问。


JVM内存初理解

区别:


以上就是本文全部内容,希望对大家有所帮助!上述内容如有不妥之处,还请读者指出,共同探讨,共同进步!

(Java在途中)

长按关注,每天java一下,进军架构师





以上是关于JVM内存初理解的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Java虚拟机(jvm性能调优+内存模型+虚拟机原理)

深入理解JVM之JVM内存区域与内存分配

深入理解JVM之JVM内存区域与内存分配

Jvm(32),理解升级----(挺不错的)图解深入理解JVM之JVM内存区域与内存分配

深入理解JVM之JVM内存区域与内存分配

深入理解JVM—JVM内存模型