JVM 内存分析

        JVM 全称 Java Virtual Machine,也就是我们耳熟能详的 Java 虚拟机。它能识别 .class后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作。JVM的特点是,跨平台、跨语言。

跨平台:不同操作系统有不同的JDK的版本实现跨平台。

跨语言:JVM只识别字节码(class文件),所以JVM跟语言是解耦的。除了java还有像Groovy 、Kotlin、Jruby等语言,也是编译成字节码,在JVM上面跑,所以JVM是跨语言的。

JRE、JDK的关系

        JRE是什么?它除了包含JVM之外,提供了很多的类库(就是我们说的jar包,它可以提供一些即插即用的功能,比如读取或者操作文件,连接网络,使用I/O等等之类的)这些东西就是JRE提供的基础类库。JVM 标准加上实现的一大堆基础类库,就组成了 Java 的运行时环境,也就是我们常说的 JRE(Java Runtime Environment)。

        但对于程序员来说,还需要调试代码,打包代码、以及反编译代码。所以要使用JDK。JDK还提供了一些非常好用的小工具,比如 javac(编译代码)、java、jar (打包代码)、javap(反编译<反汇编>)等。这个就是JDK。

JVM整体

        一个 Java 程序,首先经过 javac 编译成 .class 文件,然后 JVM 将其加载到方法区,执行引擎将会执行这些字节码。执行时,会翻译成操作系统相关的函数。JVM 作为 .class 文件的翻译存在,输入字节码,调用操作系统函数。

        过程:Java 文件->编译器>字节码->JVM->机器码。

     我们所说的 JVM,狭义上指的就 HotSpot(因为JVM有很多版本,但是使用最多的是HotSpot)。如非特殊说明,我们都以 HotSpot 为准。Java 之所以成为跨平台,就是由于 JVM 的存在。Java 的字节码,是沟通 Java 语言与 JVM 的桥梁,同时也是沟通 JVM 与操作系统的桥梁。

运行时数据区域

        Java 引以为豪的就是它的自动内存管理机制。相比于 C++的手动内存管理、复杂难以理解的指针等,Java 程序写起来就方便的多。

        在 Java 中,JVM 内存主要分为堆、程序计数器、方法区、虚拟机栈和本地方法栈。

        虚拟机栈、本地方法栈、程序计数器是线程独占的区域;方法区和堆是线程共享的。

 程序计数器

        程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。

        由于 Java 是多线程语言,当执行的线程数量超过 CPU 核数时,线程之间会根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的数器。程序计数器也是JVM中唯一不会OOM(OutOfMemory)的内存区域。

虚拟机栈

        虚拟机栈在JVM运行过程中存储当前线程运行方法所需的数据,指令、返回地址。

        Java 虚拟机栈是基于线程的。在线程的生命周期中,会频繁地入栈和出栈,栈的生命周期是和线程一样的。栈里的每条数据,就是栈帧。在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦完成相应的调用,则出栈。所有的栈帧都出栈后,线程也就结束了。

        每个栈帧,都包含四个区域:局部变量表、操作数栈、动态连接、返回地址。

       局部变量表:用于存放局部变量。Java的八大基础数据类型,一般32位就可以存放下,如果是64位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,则存放它的引用地址。

       操作数据栈:存放方法执行的操作数。如a = i+j,存放i ,j 等操作数。JVM中,基于解释执行的这种方式是基于栈的引擎,这个说的栈,就是操作数栈。

       动态连接:Java语言多态特性,在运行时确定调用具体的方法。

       返回地址:正常情况调用程序计数器中的地址作为返回地址;发生异常,通过异常处理器表来确定返回地址。

 本地方法栈

        本地方法栈跟 Java 虚拟机栈的功能类似,Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。但本地方法并不是用 Java 实现的,而是由 C 语言实现的。

       本地方法栈是和虚拟机栈非常相似的一个区域,它服务的对象是 native 方法。你甚至可以认为虚拟机栈和本地方法栈是同一个区域。虚拟机规范无强制规定,各版本虚拟机自由实现 ,HotSpot直接把本地方法栈和虚拟机栈合二为一 。

方法区

       方法区主要是用来存放比较稳定的数据,主要被虚拟机加载的类相关信息,包括类信息、静态变量、常量、运行时常量池、字符串常量池。

      JVM 在加载类的时候,会先加载 class 文件,而在 class 文件中除了有类的版本、字段、方法和接口等描述信息外,还有常量池 (Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用。当类加载到内存中后,JVM 就会将 class 文件常量池中的内容存放到运行时的常量池中;在解析阶段,JVM 会把符号引用替换为直接引用(对象的索引值)。

        运行时常量池是全局共享的,多个类共用一个运行时常量池,class 文件中常量池多个相同的字符串在运行时常量池只会存在一份。

        方法区是线程共享的。假如两个线程都试图访问方法区中的同一个类信息,而这个类还没有装入 JVM,那么此时就只允许一个线程去加载它,另一个线程必须等待。(JVM保证类加载是线程安全的)。

        永久带和元空间

        永久带和元空间都是方法区的一种实现方式。

        JDK1.7之前,方法区用永久带实现。永久带是从堆划分出来,JVM回收时要回收堆和方法区(永久带),永久带回收难度大。由于永久带中的信息在每次 FullGC 的时候都可能被收集,回收率都偏低。另外,为永久带分配多大的空间难以确定,永久代内存经常不够用或发生内存溢出,抛出异常。

        JDK1.8之后,方法区用元空间实现。元空间使用的是机器内存(也叫堆外内存或者直接内存)。元空间不受堆的限制。但是,元空间不断扩展,会挤压堆空间。如,机器共20G,元空间挤占15G,堆则只能使用5G。

        直接内存:不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;这块内存不受java堆大小限制,但受本机总内存的限制,可以通过-XX:MaxDirectMemorySize来设置(默认与堆内存最大值一样)。这块区域也会出现OOM异常。

        历史原因:Oracle收购了两个JVM: hotspot和JRocket,前者有永久带、元空间,后者没有永久带。Oracle为推出jdk1.8将两者合二为一,把永久带去掉。

        在 HotSpot 虚拟机、Java7 版本中已经将静态变量和运行时常量池转移到了堆中,其余部分则存储在方法区。     

        堆是 JVM 上最大的内存区域,几乎所有的对象都存储在堆中。垃圾回收,操作的就是堆。堆空间一般是程序启动时,就申请了,但是并不一定会全部使用。

        随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java 中,就叫作 GC(Garbage Collection)。

        对象在堆上分配,或是在栈上分配,主要和两个方面有关:对象的类型和在 Java 类中存在的位置。Java 的对象可以分为基本数据类型和普通对象。对于普通对象来说,JVM 会首先在堆上创建对象,然后在其他地方使用的其实是它的引用。对于基本数据类型来说(byte、short、int、long、float、double、char),有两种情况。当你在方法体内声明了基本数据类型的对象,它就会在栈上直接分配。其他情况,跟随对象在堆上分配。

运行时数据区工作过程

        当我们通过 Java 运行代码时,JVM 的处理过程如下:

        JVM 向操作系统申请内存,JVM 第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间。

        JVM 获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小。

        完成上一个步骤后, JVM 首先会执行构造器,编译器会在.java 文件被编译成.class 文件时,收集所有类的初始化代码,包括静态变量赋值语句、静态代码块、静态方法,静态变量和常量放入方法区。

        启动 main 线程,执行 main 方法,开始执行第一行代码。此时堆内存中会创建一个对象,对象引用就存放在栈中。

        

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值