JVM之内存管理及结构组成 - 初识篇

本文介绍关于JVM的概念、组成和内存模型的相关内容,目录结构如下:

    - 什么是跨平台

    - JVM概念

    - JVM结构组成

    - JVM的内存模型初识

首先,在理解JVM之前,我们先了解一下Java当中,人们常常提起的“跨平台”。

那,什么又是跨平台,Java又是如何实现跨平台的呢?

答:跨平台,指的就是Java编写的程序,能够在多种机器平台环境里运行,实现了一次编译好的程序,在不同的机器上运行。Java实现的跨平台机制,其实指的是Java程序的跨平台。通过JVM(C/C++所开发)的,将Java程序编译生成 .class 文件,称为字节码文件。Java 虚拟机(JVM)就是负责将字节码文件翻译成特定平台下的机器码然后运行,也就是说,只要在不同平台上安装对应的 JVM,就可以运行字节码文件,运行我们编写的 Java 程序。



而这,就是传说中的“一次编译,到处运行”。

现在,就来深入的了解一下,什么是JVM

1、什么是JVM?

简单来说,JVM (即 Java Virtual Machine,Java 虚拟机)就是 编译后的 Java 程序(.class文件)和硬件系统之间的接口或者说联系。它通过模拟一个计算机来达到一个计算机所具有的的计算功能。JVM 能够跨计算机体系结构来执行 Java 字节码,主要是由于 JVM 屏蔽了与各个计算机平台相关的软件或者硬件之间的差异,使得与平台相关的耦合统一由 JVM 提供者来实现。

而它又是怎么做到跨平台,并且能做到“与机器无关,与平台无关”呢?


原理:编译后的 Java 程序指令并不直接在硬件系统的 CPU 上执行,而是由 JVM 执行。

为什么与平台无关:JVM屏蔽了与具体平台相关的信息,使Java语言编译程序只需要生成在JVM上运行的目标字节码(.class),就可以在多种平台上不加修改地运行。Java 虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。因此实现java平台无关性。


2、JVM的组成

JVM 的结构基本上由 3 部分组成。

- 类加载器,在 JVM 启动时或者类运行时将需要的 class 加载到 JVM 中

执行引擎,执行引擎的任务是负责执行 class 文件中包含的字节码指令,相当于实际机器上的 CPU

- JVM 运行时数据区 (JVM Runtime Area) 其实就是指 JVM 在运行期间,其对JVM内存空间的划分和分配。



其中:

运行时的数据取在所有线程间共享

运行时数据取线程私有


(1)类加载器

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。


- 当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化:

- 类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。

- 当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。

它有两种装载class的方式:显式和隐式

隐式:运行过程中,碰到new方式生成对象时,隐式调用classLoader到JVM

显式:通过class.forname()动态加载

类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

加载类的过程:采用双亲委托机制(双亲委派模型)


Bootstrap class loader

当开启JVM时,这个类加载器被创建,它负责加载虚拟机的核心类库,如 java.lang.* 等。

Extension class loader 这个加载器加载出了基本 API 之外的一些拓展类。
AppClass Loader 加载应用程序和程序员自定义的类。
Custom ClassLoader 用户自定义的类加载器应该继承 ClassLoader 类。
双亲委派模型的工作过程:

a、当前 ClassLoader 首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
b、当前 classLoader 的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到 bootstrap ClassLoader.

c、当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。


思考:如何判定两个 Java 类是相同的?

Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。因此,在判断是否为同一个Java类时,必须是由同一个类加载器所加载


(2)类的加载

    a、加载

I、通过一个类的全限定名来获取其定义的二进制字节流。
II、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
III、在Java堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口。

    b、连接

I、验证:确保被加载的类的正确性
II、准备:为类的静态变量分配内存,并将其初始化为默认值
III、解析:把类中的符号引用转换为直接引用

    c、初始化

I、假如这个类还没有被加载和连接,则程序先加载并连接该类
II、假如该类的直接父类还没有被初始化,则先初始化其直接父类
III、假如类中有初始化语句,则系统依次执行这些初始化语句

    d、使用

只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

I、创建类的实例,也就是new的方式
II、访问某个类或接口的静态变量,或者对该静态变量赋值
III、调用类的静态方法
IV、反射(如 Class.forName(“com.shengsiyuan.Test”))
V、初始化某个类的子类,则其父类也会被初始化
VI、Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类

    e、卸载

I、执行了 System.exit()方法
II、程序正常执行结束
III、程序在执行过程中遇到了异常或错误而异常终止
IV、由于操作系统出现错误而导致Java虚拟机进程终止


3、JVM内存管理

(1)内存区域划分

JVM在运行时将数据划分为了6个区域来存储。heap, java stack,method area, native method stack, PC register.


a、PC程序计数器:就是个指示器一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。

b、栈用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

c、本地方法栈:与虚拟机栈的作用相似,虚拟机栈为虚拟机执行执行java方法服务,而本地方法栈则为虚拟机使用到的本地方法服务。

d、方法区
方法区和堆区域一样,是各个线程共享的内存区域,它用于存储每一个类的结构信息,例如运行时常量池,成员变量和方法数据,构造函数和普通函数的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。

这块区域对应Permanent Generation 持久代。 XX:PermSize指定大小。

e、运行时常量池

其空间从方法区中分配,存放的为类中固定的常量信息、方法和域的引用信息。

f、Java堆:
被所有线程共享的一块存储区域,在虚拟机启动时创建,它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配。
Java堆在JVM启动的时候就被创建,堆中储存了各种对象,这些对象被自动管理内存系统(Automatic Storage Management System,也即是常说的 “Garbage Collector(垃圾回收器)”)所管理。这些对象无需、也无法显示地被销毁。

JVM将Heap根据对象的生命周期长短,把堆分为3个代:Young,Old和Permanent

Young(年轻代):朝生夕死的那种对象
Tenured(年老代):顽固分子,一直被引用,一直存活着。Young:Tenured=8:1

Perm(持久代):用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

(2)内存模型

Java 内存模型(Java Memory Model, JMM),来屏蔽掉各层硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。

关于JMM的具体阐述,我将在下一篇文章当中单独阐述。涉及其指令重排序、内存屏障、内存分配机制等问题。

另外,文章内容的编写和整理,十分感谢朋友翔和Java团长推文中的部分资料摘录。

有任何建议或问题,欢迎加微信一起学习交流,欢迎从事IT,热爱IT,喜欢深挖源代码的行业大牛加入,一起探讨。

个人微信号:bboyHan

猜你喜欢

转载自blog.csdn.net/han0373/article/details/80424974