热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【深入学习JVM01】运行时数据区域划分

前言在使用c进行编程时,我们通过new创建的每一个对象都需要有对应的delete操作去释放对象所占用的内存,对内存的掌控度比较高,但是程序

前言

在使用c++进行编程时,我们通过new创建的每一个对象都需要有对应的delete操作去释放对象所占用的内存,对内存的掌控度比较高,但是程序员需要知道对象什么时候不需要使用了,并需要手动释放内存,如果忘记了delete释放,很容易出现内存泄漏(申请内存后,没有释放,会一直占用着)和内存溢出(因为过多的内存泄漏导致无法申请足够的内存,即out of memory)的问题。

相比之下,java虚拟机提供了自动内存管理机制,java程序员可以解放双手,不再需要去写delete等手动释放内存的代码,虚拟机会自动将内存中无用的对象占用的内存释放。

了解jvm的必要

虽然有自动内存管理机制的存在,但是不代表写的每个java程序都不存在内存泄漏和内存溢出问题,我们需要对虚拟机有足够的了解,才能在发生内存泄露和内存溢出的时候有效地排查问题。

本文将对jvm虚拟机运行时内存进行一个基本的介绍,后续的文章也会讲解jvm其他知识,大部分都是自己的读书总结加上自己的理解。希望将自己的所学进行总结的同时能惠及他人,如果有什么地方讲的不对,希望各位同学能够指出。

内存划分

java虚拟机将其管理的内存划分为以下几块:

  • 程序计数器 (PC Register)
  • 虚拟机栈 (JVM Stack)
  • 本地方法栈 (Native Method Stack)
  • 堆 (Heap)
  • 方法区 (Method Area)

各个区域都有其各自的特点和作用,以及不同的创建和销毁的时间

各个区域的介绍

程序计数器

  • 描述
    • 程序计数器是一个较小的内存区域
  • 作用
    • 记录着当前线程所执行的字节码行号
    • 字节码解释器在工作的时候,通过改变这个计数器的值来选取下一条要执行的字节码指令。
    • 分支,循环,跳转,异常处理,线程恢复等功能都需要使用到这个程序计数器。
  • 特点
    • 线程私有--每个线程都有一个独立的程序计数器。
    • 如果当前线程正在执行一个java方法,这程序计数器的值为虚拟机字节码指令的地址,如果执行的是一个Native 方法,这个计数器的值则为空。
    • 程序计数器是唯一没有规定OutOfMemoryError的内存区域
  • 创建时间
    • 每个线程启动的时候会创建一个较小的内存区域作为线程的程序计数器
  • 销毁时间
    • 线程结束时会释放该内存区域

扩展问题1:为什么需要程序计数器?

java虚拟机的多线程是通过线程轮转,分配CPU时间片来执行java程序,当线程切换时,为了能够回到原来的字节码执行位置继续程序的执行,所以每个线程会有一个程序计数器。

扩展问题2:Native方法是什么?

java程序执行的时候调用的方法,有些是用java语言实现的,有些是用其他语言编写实现的,用其他语言实现的方法称为Native方法本地方法,native方法会使用native关键字进行标注,如Object类的getClass()方法:

public class Object {public final native Class getClass();...
}

由于native方法不是java实现的,也就没有字节码行号之说,此时程序计数器的值应当为空(undefined)。

虚拟机栈

  • 描述
    • 虚拟机栈是描述java方法执行过程的一个内存模型
    • 具体描述:每个方法在执行的时候都会创建一个栈帧,栈帧中存储的是java方法的局部变量表、操作数栈、动态链接、方法出口等信息。java程序在执行的时候每调用一个java方法都会对应的创建栈帧并压入虚拟机栈中,当方法执行完毕,又会将栈帧从虚拟机栈中弹出。虚拟机栈就是栈帧存放的一个栈结构的内存区域。
  • 作用
    • 描述java方法执行的过程,保存栈帧。
  • 特点
    • 线程私有
    • 此区域可能会有两种内存异常情况:
      • 当栈的深度大于虚拟机所限制的最大深度,会抛出StackOverflowError异常。
      • 如果虚拟机栈动态扩展无法申请到足够的内存,就会抛出OutOfMemoryError异常。
  • 创建时间
    • 线程启动的时候
  • 销毁时间
    • 线程结束的时候

扩展1:局部变量表

局部变量表用于存放编译期可知的各种基本数据类型、对象引用、returnAddress类型(一条字节码指令的地址)

对于基本数据类型,存放的是变量的名和值;

对于引用类型,存放的是指向对象在堆中的起始地址。

ps: 对于64位的long或double类型的局部变量会占用两个局部变量表空间(Slot),其余的数据类型都是只占用一个局部变量表空间。

局部变量表所需要的空间在编译期间已经计算好了,在一个方法执行时,需要为栈帧分配多少局部变量表空间是完全确定的

本地方法栈

本地方法栈的特性和虚拟机栈几乎一样。

  • 本地方法栈与虚拟机栈的区别
    • 本地方法栈为本地方法服务
  • 本地方法栈可能出现的异常
    • 同虚拟机栈一样可能抛出StackOverflowErrorOutOfMemoryError 异常。

  • 描述
    • 堆内存的唯一目的是存放对象实例
    • 堆内存是垃圾收集的主要区域,因此也叫GC堆
  • 作用
    • 存放对象实例
  • 特点
    • 虚拟机所管理的内存中最大的一块
    • 几乎所有的对象都在堆区分配内存,当然也有例外,JIT编译器有可能会进行优化,直接在栈上分配,有关信息可以直接搜索“逃逸分析”了解,这不在本文的讨论范围内。
    • 所有线程共享的一块内存区域
    • 堆内存在物理上不一定是连续的,保证逻辑连续即可
    • 堆内存区域无法满足分配对象实例所需内存,可能抛出OutOfMemoryError异常
    • 堆内存设置固定大小也可以动态扩展,可在启动参数上指定最小大小及扩容的上限。
  • 创建时间
    • 虚拟机启动的时候就创建了堆内存

扩展1: 堆区细分

jvm为了垃圾回收的方便,将堆划分为新生代老年代,新创建的对象基本上都放在新生代中,而存活比较久的对象则会移到老年代中。新生代和老年代采用不同的垃圾收集算法,可以更高效地回收内存。采用复制算法的新生代还可以细分为EdenFrom SurvivorTo Survivor。具体的详情是怎样的,为了不偏离这篇文章的主旨,这里先打个问号,后序的文章将会详细介绍堆区的几个划分的用途。

堆区虽然是线程共享的,但是如果设定了启动参数-XX:+UseTLAB,则开启了本地线程分配缓冲(Thread local Allocation Buffer, TLAB),会为每个线程单独在堆中划分出一个TLAB,哪个线程需要分配内存,就先在该线程对应的TLAB中分配内存,当TLAB用完,才在堆区的Eden中继续申请一块TLAB

方法区

方法区是用于存放虚拟机加载的类信息、常量、静态变量、编译后的代码等数据。

方法区特点:

  • 线程共享
  • 方法区大小可固定也可以动态扩展。
  • 与堆区一样不需要连续的物理内存,但要求逻辑连续。
  • 该区域的垃圾收集目标主要是针对运行时常量池的回收和对类进行卸载
  • 可能出现OutOfMemoryError异常。

扩展1:运行时常量池:

class文件中有个常量池,运行时常量池就是class文件中常量池经过类加载后存放的内存区域。

常量池主要存放两类常量:字面量和符号引用。

字面量指字符串,声明为final的常量值等;而符号引用是java编译后生成的各种常量,其包括:

  • 类和接口的全限定名
  • 成员变量的名称和描述符
  • 方法的名称和描述符

jdk1.8之前,方法区是用永久代实现的, 在jdk1.7以下的版本,运行时常量池是方法区的一部分,而jdk1.7及之后的版本,运行时常量池中的字符串常量池已经不在方法区,而是在java堆中开辟了一块区域作为字符串常量池。

在jdk1.8开始,已经没有永久代的概念,譬如符号引用(Symbols)转移到了native 堆中的元空间;字面量也在 java heap;类的静态变量(class statics)转移到了java heap

扩展2:常量是否只能在编译期产生? 否,运行期也可能将新的常量放入运行时常量池中,比如Stringintern方法。在jdk1.7的表现如下:

// 如果运行时常量池中,存在"10"这个字符串常量
// 则将常量池中的字符串对象返回,
// 如果不存在,则直接在运行时常量池中创建“10"这个字符串,并将其返回。
String s = String.valueOf(10).intern();

直接内存

前面讲的几块都属于虚拟机管理的运行时数据区域,java程序中也有可能会用到不是虚拟机运行时内存区域的一部分。这块内存我们通常称为直接内存

  • 直接内存不受java堆大小的限制,但是受本机物理内存的限制。

  • 直接内存也可能导致出现OutOfMemoryError异常。

直接内存的例子: jdk 1.4 加入的NIO类,引入了一种基于通道Channel和缓冲区Buffer的IO方式。直接通过Native方法在java堆外的直接内存中分配内存, 通过存储在java堆中的DirectByteBuffer对象作为这块直接内存的引用。操作DirectByteBuffer即可操作直接内存,这样做的好处是避免了要使用直接内存的时候需要先复制到java堆中。直接操作直接内存更加高效。

转:https://juejin.im/post/5b8372d96fb9a019c771656a



推荐阅读
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • 在分析Android的Audio系统时,我们对mpAudioPolicy->get_input进行了详细探讨,发现其背后涉及的机制相当复杂。本文将详细介绍这一过程及其背后的实现细节。 ... [详细]
  • 字节流(InputStream和OutputStream),字节流读写文件,字节流的缓冲区,字节缓冲流
    字节流抽象类InputStream和OutputStream是字节流的顶级父类所有的字节输入流都继承自InputStream,所有的输出流都继承子OutputStreamInput ... [详细]
  • 基于Linux开源VOIP系统LinPhone[四]
    ****************************************************************************************** ... [详细]
  • 深入解析Java虚拟机的内存分区与管理机制
    Java虚拟机的内存分区与管理机制复杂且精细。其中,某些内存区域在虚拟机启动时即创建并持续存在,而另一些则随用户线程的生命周期动态创建和销毁。例如,每个线程都拥有一个独立的程序计数器,确保线程切换后能够准确恢复到之前的执行位置。这种设计不仅提高了多线程环境下的执行效率,还增强了系统的稳定性和可靠性。 ... [详细]
  • 使用Maven JAR插件将单个或多个文件及其依赖项合并为一个可引用的JAR包
    本文介绍了如何利用Maven中的maven-assembly-plugin插件将单个或多个Java文件及其依赖项打包成一个可引用的JAR文件。首先,需要创建一个新的Maven项目,并将待打包的Java文件复制到该项目中。通过配置maven-assembly-plugin,可以实现将所有文件及其依赖项合并为一个独立的JAR包,方便在其他项目中引用和使用。此外,该方法还支持自定义装配描述符,以满足不同场景下的需求。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 本文探讨了如何在 Java 中将多参数方法通过 Lambda 表达式传递给一个接受 List 的 Function。具体分析了 `OrderUtil` 类中的 `runInBatches` 方法及其使用场景。 ... [详细]
  • 字符串学习时间:1.5W(“W”周,下同)知识点checkliststrlen()函数的返回值是什么类型的?字 ... [详细]
  • 开发日志:高效图片压缩与上传技术解析 ... [详细]
  • 深入解析Android 4.4中的Fence机制及其应用
    在Android 4.4中,Fence机制是处理缓冲区交换和同步问题的关键技术。该机制广泛应用于生产者-消费者模式中,确保了不同组件之间高效、安全的数据传输。通过深入解析Fence机制的工作原理和应用场景,本文探讨了其在系统性能优化和资源管理中的重要作用。 ... [详细]
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
author-avatar
万象新动HR
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有