关注微信公众号:CodingTechWork,一起学习进步。

引言

  我们经常会被问到一个问题是Java和C++有何区别?我们除了能回答一个是面向对象、一个是面向过程编程以外,我们还会从底层内存管理和垃圾收集方面作出比较。

  对于C++而言,程序员既要做程序设计开发又要维护底层内存管理;而对于Java而言,程序员不需要控制底层,只需要安心写自己的代码即可,因为Java虚拟机自动实现了内存管理以及垃圾回收。

  但是,我们写的程序或者程序环境问题等也时长出现内存泄露和溢出,这个时候程序员如果不知道虚拟机如何分配和管理内存,排查问题将举步维艰。下面我们就来一起看看,Java虚拟机内存是如何划分的。

运行时数据区

概念

  Java虚拟机在执行Java程序时,会将内存区域划分为不同的数据区域(运行时数据区)。有些数据区域是跟随VM进程的启动而存在,有的区域是跟随用户线程的启动和结束而建立和销毁。

分类

  一般运行时数据区分为:程序计数器、虚拟机栈、本地方法栈、Java堆、方法区、运行时常量池。

结构

内存划分



程序计数器

定义

  程序计数器(Program Counter Register)是一块较小的内存空间,是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选择下一条需要执行的字节码指令(分支、循环、跳转、异常处理、线程恢复等功能)

线程私有

  多线程是通过线程轮换交替并分配处理器执行时间的方式实现,任何时刻,一个处理器都只会执行一条线程的指令,每条线程需要一个独立的程序计数器,这样各条线程之间的计数器互不影响,独立存储,从而保证线程切换后可以恢复到正确的执行位置。
  反例:线程A和线程B交替执行,线程A计数到5,切换执行,线程B计数到9,如果两个线程共享程序计数器,则线程A此时计数共享线程B的计数值9,则无法恢复到正常的执行位置。
  正例:线程A和线程B交替执行,线程A计数到5,切换执行,线程B计数到9,线程A再切换执行时,继续计数到5开始递增,恢复到正确的执行位置。

异常

  此内存区域无OutOfMemoryError异常。

用途

  若线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若线程正在执行的Native方法,则计数器值为空。

Java虚拟机栈

定义

  虚拟机栈(Java Virtual Machine Stacks)是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法的从调用一直到执行完成,都对应着一个栈帧在VM栈中入栈到出栈的过程。

结构

线程私有

  Java虚拟机栈是线程私有的,其生命周期和线程同步,随着线程的启动而创建,随线程的结束而销毁。

局部变量表

  Loca Variable Table,虚拟机栈中的局部变量表通常就是所说的虚拟机栈,是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。其中存放了编译期间可知的基本数据类型、对象引用类型和方法返回地址类型。
  局部变量表所占用的内存空间是在编译期间就已经分配好了,进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是确定的,在方法运行期间也不会去改变这个局部变量表的大小。
  基本数据类型:8种基本数据类型是boolean、byte 8位、char、short 16位、int 32位、float 32位、long 64位、double 64位。其中64位长度的long和double类型数据会占用2个局部变量空间(slot),其余的都是占用1个slot。
  对象引用类型:reference类型,与对象引用不等价,一般是指向对象起始地址的引用指针,或者是指向一个代表对象的句柄、其他与此对象相关的的位置。
  方法返回地址类型:returnAddress类型指向一条字节码指令的地址。

操作数栈

  Operand Stack,操作数栈也称为操作栈,是一个后入先出栈,其中村的每个元素可以是任意的Java数据类型。当一个方法刚开始执行时,该方法的操作数栈是空的,当方法执行过程中,会有各种字节码指令往操作数栈中写入和提取内容(入栈/出栈操作)

动态链接

  Dynamic Linking,每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的应用,持有这个引用就是为了支持方法调用过程中的动态链接,常量池中的符号引用在每一次运行期间转化为直接引用即为动态链接。

方法返回地址

  returnAddress,当一个方法开始执行后,会有两种方式退出该方法:正常完成出口异常完成出口
  正常完成出口:程序执行过程中遇到任意一个方法返回的字节码指令,返回值传递给上一层调用者,正常退出程序方法。
  异常完成出口:程序方法执行过程中遇到异常,这个异常没有在方法体内得到处理(没有try...catch,没有throw异常),导致方法退出。

异常

  StackOverflowError异常:线程请求的栈深度大于虚拟机所允许的深度,抛出该异常。
  OutOfMemoryError异常:虚拟机可以动态扩展时,不能够扩展申请到足够的内存,抛出该异常。

本地方法栈

定义

  本地方法栈(Native Method Stack)是为虚拟机使用的Native方法服务

异常

  StackOverflowError异常:线程请求的栈深度大于虚拟机所允许的深度,抛出该异常。
  OutOfMemoryError异常:虚拟机可以动态扩展时,不能够扩展申请到足够的内存,抛出该异常。

与虚拟机栈异同


  虚拟机栈是为VM执行Java方法(字节码)服务;本地方法栈是为VM执行的Native方法。
:
  与虚拟机作用类似,有的VM是将两者合二为一,都会抛出StackOverFlowErrorOutOfMemoryError异常。

Java堆

定义

  Java堆(Java Heap)是JVM中最大的一块内存,是存放对象实例的区域。所有对象实例以及数组都要在堆上分配。当然,也有特特殊的情况,JIT编译器的发展与逃逸分析技术会促进栈上分配及变量替换优化技术的发展。

结构

线程共享

  Java堆是被所有线程共享的一块内存区域,在VM启动时创建。线程共享可以将Java堆划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。

GC堆

  Garbage Collected Heap,Java堆是垃圾收集器管理的主要区域,简称GC堆。Java堆细分为新生代和老年代,再细分为Eden空间、From Survivor空间、To Survivor空间。

堆空间

  Java堆可以处于物理上不连续的内存空间,只需要逻辑连续即可,有点类似于磁盘空间。实现时,既可以是固定大小,也可以扩展(-Xmx和-Xms参数进行控制)。

异常

  若堆中没有内存完成实例分配,堆也无法扩展时,会抛出OutOfMemoryError异常。

方法区

定义

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

线程共享

  和Java堆一样,也是线程共享区域。

持久代

  对于HotSpot虚拟机而言,方法区可以称作为“永久代”或者“持久代”,这是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至了方法区,使用永久代来实现方法区,垃圾收集器可以像管理Java堆一样管理这部分内存区域。

限制

  方法区和Java堆一样不需要连续的物理内存空间,可以选择固定大小也可以选择扩展,同时,可选择不实现垃圾收集,方法区永久代中的数据不代表永久存在,该内存区域的内存回收目标是针对常量池的回收和对类型的卸载

运行时常量池

  运行时常量池(Runtime Constant Pool)是方法区的一部分,相比较而言,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(Constant Pool Table)Class文件常量池用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
  JVM规范对运行时常量池限制比较宽松,不同的厂商可以根据自己的需求自行实现该内存区域,一般而言,除了保存Class文件中描述的符号引用外,还会将转化出的直接引用存储在其中。
  与Class文件常量池相比较而言,运行时常量池还具备动态性,常量不一定在编译器产生,即并非预置入Class文件常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池子中,如String类的intern()方法。

异常

  无法申请到内存时,会抛出OutOfMemoryError异常。

补充

直接内存(Direct Memory)

  1. 该区域不属于虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存预期,但会频繁使用且导致OutOfMemoryError
  2. JDK1.4中新加入的NIO(New Input/Output)类,引入一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作(可避免在Java堆和Native堆中来回复制数据
  3. 不会受到Java堆大小的限制,但受本机总内存大小以及处理器寻址空间的限制。可通过-Xmx等参数来设置实际内存大小,在我们实际操作过程中,经常忽视直接内存,使得整个内存区域总和大于物理内存限制,导致OutOfMemoryError异常。

总结

运行时数据区 线程是否私有 作用 异常
程序计数器 线程私有 每个线程都有自己的程序计数器,是当前线程所执行的字节码的行号指示器。
虚拟机栈 线程私有 是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用户存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就是对应着一个栈帧在虚拟机栈中入栈和出栈的过程。 若线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;若虚拟机可以动态扩展,而扩展时无法申请到足够的内存,就抛出OutOfMemoryError异常
本地方法栈 线程私有 为虚拟机使用本地(Native)方法服务 同虚拟机栈
Java堆 线程共享 在虚拟机启动时创建,该区域唯一目的是存放对象实例,几乎所有实例都是在这里分配内存;gc的主要区域 若堆中没有内存完成实例分配且堆无法再扩展时,将会抛出OutOfMemoryError异常。
方法区 线程共享 存储已被VM加载的类信息、常量、静态变量、即时编译器后的代码等数据 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常

至此,JVM运行时数据区全部总结完毕。

参考
《深入理解Java虚拟机》

JVM——内存区域:运行时数据区域详解的更多相关文章

  1. JVM 内存区域 (运行时数据区域)

    JVM 内存区域 (运行时数据区域) 链接:https://www.jianshu.com/p/ec479baf4d06 运行时数据区域 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内 ...

  2. 一. JVM发展史,运行时数据区域,四大引用

    一.JVM的出现 JVM将字节码解释成不同os下的机器指令,有了jvm,java语言在不同平台上运行时不需要重新编译 虚拟机发展史 (1)Sun Classic classic jvm要么采用纯解释器 ...

  3. JVM 运行时数据区详解

    一.运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同数据区域. 1.有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,所有的线程共享这些数据区. 2.第二种则 ...

  4. [二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义

    前言简介 class文件是源代码经过编译后的一种平台中立的格式 里面包含了虚拟机运行所需要的所有信息,相当于 JVM的机器语言 JVM全称是Java Virtual Machine  ,既然是虚拟机, ...

  5. JVM内存结构——运行时数据区

    在Java虚拟机规范中将Java运行时数据划分为6种,分别为: PC寄存器(程序计数器) Java栈 堆 方法区 运行时常量池 本地方法栈 一.PC寄存器(程序计数器) PC寄存器(Program C ...

  6. jvm内存模型(运行时数据区)

    运行时数据区(runtime data area) jvm定义了几个运行时数据区,这些运行时数据区存储的数据,供开发者的应用或者jvm本身使用.按线程共享与否可以分为线程间共享和线程间独立. 线程间独 ...

  7. 1、JVM 内存模型+运行时数据区+JVM参数

    JMM(内存模型)  1.’主内存+每个线程有自己的内存 JVM运行时数据区 包含:1.程序计算器(每个线程自带):2.JAVA-STACK(每个线程自带):3.本地方法stack:4.堆:5.方法区 ...

  8. Java 虚拟机运行时数据区详解

    本文摘自深入理解 Java 虚拟机第三版 概述 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟 ...

  9. JAVA虚拟机运行时内存划分--运行时数据区域

    Java虚拟机在执行java程序时会把内存划分为以下几个不同的数据区域: java虚拟机内存划分(运行时)1.线程私有的: 程序计数器(Program Counter Register):可以看作当前 ...

  10. Java内存区域-- 运行时数据区域

    jvm在执行Java程序时,会把它所管理的内存划分为若干个不同的数据区.这些区域都有各自的用途,以及创建和销毁的时间. 有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销 ...

随机推荐

  1. C# WPF - MVVM实现OPC Client管理系统

    前言 本文主要讲解采用WPF MVVM模式设计OPC Client的过程,算作对于WPF MVVM架构的学习记录吧!不足之处请不吝赐教,感谢! 涉及知识点 C#基础 Xaml基础 命令.通知和数据绑定 ...

  2. #linux vscode 保存总提示“Retry as sudo”

    linux中,对不同路径下的文件,系统默认指定了不同的操作权限(读/写/执行),出现这个问题是由于文件的权限不足造成的.(路径为/opt/lampp/htdocs/LearnPHP_jayce/hel ...

  3. 微信小程序-返回并更新上一页面的数据

    小程序开发过程中经常有这种需求,需要把当前页面数据传递给上一个页面,但是wx.navigateBack()无法传递数据. 一般的办法是把当前页面数据放入本地缓存,上一个页面再从缓存中取出. 除此之外还 ...

  4. Spring系列.事务管理原理简析

    Spring的事务管理功能能让我们非常简单地进行事务管理.只需要进行简单的两步配置即可: step1:开启事务管理功能 @Configuration //@EnableTransactionManag ...

  5. 大型ECShop安装搬家升级错误问题最全攻略

    [引子] 最近将ECShop框架网站从租用服务器搬家至阿里云,虽然模块及功能上已经被修改的面目全非了,但基础部分还在. 在这个过程中遇到了很多的WARNING与ERROR,解决方案如下. [环境] 服 ...

  6. 底层剖析Python深浅拷贝

    底层剖析Python深浅拷贝 拷贝的用途 拷贝就是copy,目的在于复制出一份一模一样的数据.使用相同的算法对于产生的数据有多种截然不同的用途时就可以使用copy技术,将copy出的各种副本去做各种不 ...

  7. openstack Rocky 社区版部署1.2 安装ntp service

    一.controller节点安装ntp 1 安装ntp服务 yum install chrony 2 Edit the chrony.conf file and add, change, or rem ...

  8. python文件处理-检查文件名/路径是否正确

    内容涉及:检查路径是否存在,文件名长度是否一直,将重复的文件夹重命名 # -*- coding: utf-8 -*- import os import sys import numpy as np i ...

  9. linux 测试端口是否可通

    windows上一般用telnet 如telnet ip port linux上可以用telnet,跟windows一样 telnet ip port 也可以用wget:如:wget ip:port ...

  10. 超简单集成ML kit 实现听写单词播报

    背景   相信我们大家在刚开始学习一门语言的时候都有过听写,现在的小学生学语文的时候一项重要的课后作业就是听写课文中的生词,很多家长们都有这方面的经历.不过一方面这种读单词的动作相对简单,另一方面家长 ...