JVM自动内存管理机制——Java内存区域

一、JVM运行时数据区域概述

  Java相比较于C/C++的一个特点就是,在虚拟机自动内存管理机制的帮助下,我们不需要为每一个操作都写像C/C++一样的delete/free代码,所以也不容易出现内存泄漏和内存溢出的问题。显然,这里的不容易只是相对而言的,如果我们想要降低这种代码隐患的发生,就需要对Java虚拟机怎样使用内存有了解,这样的话就算产生错误,排查起来也会相对容易。下面我们来说一说JVM运行时数据区域

  

  1、程序计数器(PC寄存器): 被看作是当前线程所执行的字节码的行号指示器,字节码解析的时候就通过改变这个计数器的值来选取下一条需要执行的指令(包括分支、循环、跳转、异常处理等等都依赖PC)。在多线程程序中,每条线程都需要拥有一个独立的程序计数器,所以程序计数器是线程私有的。(如果程序正在执行一个Java方法,那么这个计数器记录的就是当前执行的字节码指令的地址;如果执行的是一个Native方法,那么计数器为Undefined),该内存区域是JVM规范中唯一一个没有规定OutOfMemoryError的区域。

  2、Java虚拟机栈:虚拟机栈描述的是Java方法执行的内存模型,这块区域也是线程私有的,生命周期和线程相同。每个方法在执行的时候会创建栈帧,用来存放局部变量表、操作数栈、动态链接、返回值等信息。(我们常说的在Java中的栈内存就是指这块区域)。在JVM规范中:如果线程请求栈深度大于虚拟机提供的深度,那么抛出StackOutflowError异常;如果无法申请得到足够的内存,会抛出OutofMemoryError异常

  3、本地方法栈:与Java虚拟机栈不同的是,本地方法栈为虚拟机执行Native方法服务,而Java虚拟机栈为虚拟机执行Java方法服务。由于JVM规范并没有对本地方法栈做强制规定,所以如同HotSpot一样,直接将本地方法栈和虚拟机栈合二为一。异常情况和JAVA虚拟机栈相同

  4、Java堆:Java堆是被所有线程所共享的一块内存区域,在虚拟机启动时候创建,这块区域的目的就是存放对象实力,基本上我们所创建的所有的对象实例都要在这里分配内存。我们下面会详细的介绍Java堆

  5、方法区:方法区和Java堆一样,是各个线程共享的内存区域,

  6、运行时常量池:运行时常量池是方法区的一部分,在Class文件中(存在类的版本、字段、方法、接口等)存在常量池的信息,作用就是用于存放编译期间生成的各种字面量和符号引用。受到方法区内存的限制,当无法申请到内存时候会抛出OutOfMemoryError异常

  还有一部分就是直接内存,但是直接内存并不是运行时数据区域的一部分,在Java的NIO库中允许Java程序员频繁的使用直接内存,从而提高性能(避免了Java堆和Native堆之间来回复制数据)。

二、再探Java堆、栈、方法区

  1、Java堆:Java堆的存在是为了解决数据存储的问题,堆中存放了对象实例。堆也是垃圾收集器管理的主要区域(所以也可以被称为GC堆)。从内存回收的角度来讲,采用分代回收算法的JVM,在堆中可被分为新生代(新生的对象或者年龄不大的对象)和老年代(老年对象),这里面的划分是按照垃圾收集器的次数,来判断对象的年龄。新生代中又被分为Eden区、s0区(from区域)、s1区(to区域),From和To是两块大小相等,可以互换角色的区域。一般来说,新生的对象会被首先分配在Eden区,然经过一次GC之后(如果对象还存活)会到from或者to区。之后类似的每一次回收,都会加1,当对象达到一定年龄后,会进入老年代。

  2、栈:Java栈是一个线程私有的空间,一般情况下一个栈由3部分组成:局部变量表、操作数栈、帧数据区。

  局部变量表:里面存放的是报错函数的参数以及局部变量;

  操作数栈:其中保存计算过程中的中间结果,同时作为计算过程中变量的临时存储空间;

  帧数据区:除了局部变量表和操作数栈之外,还需要一些数据来支持常量池的解析,帧数据区中保存着访问常量池的指针,方便程序访问常量池。除此之外,当函数返回或者出现异常时,JVM必须有一个异常处理表,方便发送异常的时候找到异常的代码(从而异常处理也是帧数据区的一部分)

  3、方法区:方法区是一块所有线程共享的内存区域,他保存了类的信息(类的字段、方法、常量池等等),方法区大小决定了系统可以保存多少个类。

  4、一张简略的图描述一下堆、栈、方法区之间的关系

三、探秘JVM堆中对象分配

  1、对象的创建

  a)我们从new开始,当虚拟机遇到一条new指令的时候,会首先检查这个指令的参数能否在常量池中找到这个类的符号引用,并且检查这个类的加载是否被加载、解析、初始化过,如果没有就会按照类加载过程进行相应类的加载。

  b)在类加载完毕后,然后JVM对新生对象分配内存,对象的分配简单而言就是在将一块确定大小的内存从Java堆中分配出来。

  ①堆是完整的(这时候所有使用的内存在一边,没有使用的内存在一边,中间放着一个指针作为分界点的指示器):分配内存就只是把指针向空闲内存那边移动与对象大小一样大的距离(这种方式成为“指针碰撞”)

  ②堆不是完整的(使用和未用的相互交错):虚拟机需要维护一个列表,其中记录的是空间内存的状况,在分配的时候从空闲内存中找到一块足够的空间划分给对象实例,然后更新表中的记录信息(这种方式成为“空闲列表”)

  c)考虑多线程情况下的对象内存分配

  在并发的情况下是线程不安全的,可能出现正在给对象A进行分配,这时候指针位置还没有来得及改变,然后这时候对象B的内存分配又使用了原来的指针记性分配。在《深入理解Java虚拟机》中讲到两种解决方案

  ①对对象的分配进行同步处理,采用CAS配置失败重试的方式保证更新操作的原子性

  ②将内存分配的动作按照线程划分在不同的空间中进行。即保证每个线程预先在Java堆中分配一小块空间(本地线程分配缓冲TLAB)。线程需要分配的时候,就先在TLAB上面分配,然后当TLAB使用完毕再进行同步锁定。

  d)内存分配完成之后,虚拟机需要将分配的内存初始化为零值,这一步保证了对象实例字段在Java代码中可以不被赋初值就使用,使得程序能够访问到这些字段的数据类型对应的零值。

  e)然后虚拟机需要对对象进行必要的设置,比如对象是那个类的实例,类的元数据、对象哈希码、GC年龄等等存放在对象头中。  

  f) 上面执行完毕之后,从虚拟机角度而言已经产生了心得对象。但是程序中对象创建还没有执行<init>方法,所有字段均为零值。所以,执行完new指令,还需要执行<init>方法,按照程序的角度进行初始化,才能使用这个对象。

  2、对象的访问定位

  我们在介绍堆栈的时候就介绍过堆栈和方法区之间的关系,Java程序中对象的引用存放在栈(reference)中,引用的实例存放在Java堆中(使用栈上面的对象引用来操作堆上面的具体对象),但是我们并没有定义怎样通过引用去定位、访问堆中的具体对象位置,下面介绍句柄和直接指针的方式

  a)句柄方式

  首先在Java堆中分配一块区域作为句柄池,栈中的reference中存放的就是对象的句柄地址信息(句柄中包含的是对象实例数据和类型数据各自的具体地址信息)。使用句柄方式的好处就是reference中存储的是稳定的句柄地址信息,而reference本身不需要修改。下面是句柄方式的简略图

  

  b)直接指针方式:

  Java堆中对象的布局中必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象的地址。使用直接指针的方式就是存取速度快,节省了一次指针定位的时间。

   

原文地址:https://www.cnblogs.com/fsmly/p/10356492.html

时间: 2024-11-05 14:52:49

JVM自动内存管理机制——Java内存区域的相关文章

JVM自动内存管理机制——Java内存区域(下)

一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a)下面是一些简单的使用参数 其中最后一个是一个运行时参数设置的简单实例.一般-XX是系统级别的配置(日志信息,或者是配置使用什么样的垃圾回收器等等),后面跟上+表示启用.不是-XX基本上是对于应用层面的配置信息 下面是一个简单的实例:表示设置初始堆大小为5M,最大堆大小为20M,并将虚拟机的参数设置打印出来,后

2.1 自动内存管理机制--Java内存区域与内存溢出异常

自动内存管理机制 第二章.Java内存区域与内存溢出异常 [虚拟机中内存如何划分,以及哪部分区域.什么样代码和操作会导致内存溢出.各区域内存溢出的原因] 一.运行时数据区域 Java虚拟机所管理的内存包括以下几个运行时数据区域[虚拟机内存模型]: 1.程序计数器: 可以看作是当前线程所执行的字节码的行号指示器.在虚拟机中,字节码解释器工作时就是通过程序计数器的值来选择下一条需要执行的字节码指令.Java虚拟机中多线程是通过线程轮流切换并分配处理机执行时间的方式实现的,在任何一个确定的时刻,一个处

Java虚拟机内存管理机制

自动内存管理机制 Java虚拟机(JVM)在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则是依赖用户线程的启动和结束而建立和销毁.根据<Java虚拟机规范 第2版>规定,运行时数据区包括: 1.程序计数器 一块较小的内存空间,不在Ram上,而是直接划分在CPU上的,程序员无法直接操作它.当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码指令.每条

【Java深入研究】3、JVM内存管理机制

转自:http://blog.csdn.net/lengyuhong/article/details/5953544 近期看了看Java内存泄露的一些案例,跟原来的几个哥们讨论了一下,深入研究发现JVM里面还是有不少以前不知道的细节,这里稍微剖析一下.先看一看JVM的内部结构-- 如图所示,JVM主要包括两个子系统和两个组件.两个子系统分别是Class loader子系统和Execution engine(执行引擎) 子系统:两个组件分别是Runtime data area (运行时数据区域)组

java内存管理机制

JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间.释放 :对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作.但同时,它也加重了JVM的工作.因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请.引用.被引用.赋值等,GC都需要进行监控. 2. 

java内存管理机制(一)-运行时数据区

前言 本打算花一篇文章来聊聊JVM内存管理机制,结果发现越扯越多,于是分了三遍文章(文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8),本文为其中第一篇.from java内存管理机制(一)-运行时数据区  1. java内存管理机制-运行时数据区 2. java内存管理机制-内存分配 3. java内存管理机制-垃圾回收 正文 C++与java之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外的人想进去,墙里的人却想出来…… 与C.C++程序员时刻要关注着内存的分配与释放

你必须了解的java内存管理机制(三)-垃圾标记

本文在个人技术博客不同步发布,详情可用力戳 亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩... 相关链接(注:文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8) 1. 你必须了解的java内存管理机制-运行时数据区 2. 你必须了解的java内存管理机制-内存分配 3. 你必须了解的java内存管理机制-垃圾标记 前言 前面花了两篇文章对JVM的内存管理机制做了较多的介绍,通过第一篇文章先了解了JVM的运行时数据区,然后在第二篇文章中通过一个创建对象的实例介

深入理解java虚拟机二,内存管理机制

java 虚拟机自动内存管理. java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同区域 1 程序计数器 每个线程都有一个独立的计数器,用来指示需要执行的字节码的位置. 2 虚拟机栈 虚拟机栈是用来描述java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧用于储存局部变量表,操作栈,动态链接,方法出口等信息. 每一个方法被调用直至执行完成的过程,就对应着一个栈帧从虚拟机栈中从入栈到出栈的过程. 虚拟机栈线程私有,声明周期和线程一样. 局部变量表所需的内存空间在

Java面试准备之JVM详细研究一(Java内存区域)

Object obj = new Object(); 主要参考资料:<深入理解Java虚拟机>,未经本人及原书作者同意禁止转载. JVM中的数据区域 JVM中给数据分了这么几个区域: 其中: 1.程序计数器: 程序计数器是一块较小的内存空间,为当前线程所执行的字节码的行号指示器.也就是说,程序再翻译成为字节码了之后,分支.循环.跳转.异常处理.线程恢复等功能都需要程序计数器来完成. (注意!)此区域存储的东西是执行的虚拟机的字节码指令的地址,如果为natvie方法(natvie:一个调用非Ja