最近利用工作之余学习研究了一下java的内存管理机制,在这里记录总结一下。

1-1、java内存区域

当java程序运行时,java虚拟机会将内存划分为若干个不同的数据区域,这些内存区域创建和销毁的时间各不相同,所承担的功能也不相同,他们各司其职,各尽所责。这些区域的划分如下图

运行时数据区主要有五个区,分别是 堆 ,方法区,虚拟机栈,本地方法栈,程序计数器 ,下面我来一一详细讲解这五个数据区

java堆是java虚拟机管理内存中最大的一块,它是被所有线程共享的一块内存区域,在虚拟机启动时创建, 此内存的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在堆分配内存 。

java虚拟机规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。在实现时,既可以实现固定大小的,也可以是扩展的,可以 通过配置-Xmx和-Xms来扩展大小 。如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError

方法区

方法区也是被所有线程共享的一块内存区域,在Java虚拟机规范中,方法区是堆的逻辑组成部分,但他又被与堆区分开来,别名称为Non-Heap,它主要的存储内容有下面几点

* 类型的完整有效名
* 类型直接父类的完整有效名
* 类型的修饰符(public,abstract,final的某个子集)
* 类型的常量池
* 域(Field)信息
* 方法(Method)信息
* 除了常量外的所有静态(static)变量

总结起来就是主要用于存储已被虚拟机加载的类信息,常量,静态变量,编译器编译后的代码等数据

这里我在介绍一下常量池,域信息和方法信息

* 常量池
常量池也称为运行时常量池(Runtime Constant Pool),用于存放编译期生成的各种字面量和符号引用,它是这个类型用到的常量的一个有序集合,包括 实际的常量(String, Integer, 和Floating point常量)和类型,域和方法的符号引用 。
池中的数据项像数组项一样,是通过索引访问的。 因为常量池存储了一个类类型所使用到的所有类型,域和方法的符号引用,所以它在java程序的动态链接中起了核心的作用 * 域(Field)信息
域的相关信息包括: 域名; 域类型; 域修饰符(public, private, protected,static,final volatile,transient的某个子集) * 方法(Method)信息
方法的相关信息包括: 方法名, 方法的返回类型(或 void), 方法参数的数量和类型(有序的),方法的修饰符(public, private, protected, static, final, synchronized, native, abstract的一个子集) ,除了abstract和native方法外,其他方法还有保存方法的 字节码(bytecodes) 操作数栈和方法栈帧的局部变量区的大小 。

java虚拟机规范对方法区的限制比较宽松,除了和java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不是实现垃圾收集。垃圾收集行为在方法区也比较少出现,当方法区无法满足内存分配时,会抛出 OutOfMemoryError

虚拟机栈

虚拟机栈是线程私有的,它的生命周期与线程相同,当我们start一个线程时,jvm会为当前线程开辟一块虚拟机栈,当当前线程死亡时,线程的虚拟机栈也会销毁。

代码中每个方法在执行的同时,都会创建一个栈帧(Stack Frame)用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表存放了编译期可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用和returnAddreass类型

JVM对这个区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出SstackOverFlowError异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryErro异常

本地方法栈

本地方法栈和虚拟机栈的作用是一样的,只不过本地方法栈是虚拟机执行java方法时开辟的栈,而本地方法栈是虚拟机用到Native方法时,开辟的栈。

程序计数器

程序计数器是一块较小的内存,它可以看作是当前线程的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取吓一跳需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能。

由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程指尖计数器互不影响,独立存储。

1-2、对象创建

了解了内存的数据区域,我们可以进一步了解对象是如何创建的了。这里先通过一张流程图一窥java的对象创建过程



可以看到,当虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经加载。如果没有,则执行加载。

加载完成后,便会在堆中为对象分配内存,JVM有两种分配方式 ①指针碰撞,②空闲列表 ,下面我详细讲讲这两种分配方式。

* 指针碰撞
当java堆中内存是整齐的,所有用过的内存都放一边,空闲的内存放在另一边,中间放着一个指针座位分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边摞动一段与对象大小相等的距离,这种分配就叫指针碰撞
* 空闲列表
当Java堆的内存并不是完整的,已分配的内存和空闲内存相互交错,JVM通过维护一个列表,记录可用的内存块信息,当分配操作发生时,从列表中找到一个足够大的内存块分配给对象实例,并更新列表上的记录。这种分配方式称为空闲列表

当JVM所采用的垃圾收集器带有压缩整理功能时,java堆是规整的,这个时候会采用指针碰撞分配内存,否则会采用空闲列表分配内存。对象创建是一个非常频繁的行为,进行堆内存分配时还需要考虑多线程并发问题,可能出现正在给对象A分配内存,指针或记录还未更新,对象B又同时分配到原来的内存,解决这个问题有两种方案:

* 采用CAS保证数据更新操作的原子性;
* 把内存分配的行为按照线程进行划分,在不同的空间中进行,每个线程在Java堆中预先分配一个内存块,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB);

java内存管理机制剖析(一)的更多相关文章

  1. 你必须了解的java内存管理机制(二)-内存分配

    前言 在上一篇文章中,我们花了较大的篇幅去介绍了JVM的运行时数据区,并且重点介绍了栈区的结构及作用,相关内容请猛戳!在本文中,我们将主要介绍对象的创建过程及在堆中的分配方式. 相关链接(注:文章讲解 ...

  2. 你必须了解的java内存管理机制(一)-运行时数据区

    前言 本打算花一篇文章来聊聊JVM内存管理机制,结果发现越扯越多,于是分了四遍文章(文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8),本文为其中第一篇.from 你必须了解的java内存 ...

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

    本文在个人技术博客不同步发布,详情可用力戳 亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩... 相关链接(注:文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8) ...

  4. 你必须了解的java内存管理机制(四)-垃圾回收

    本文在个人技术博客不同步发布,详情可用力戳 亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩... 相关链接(注:文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8) ...

  5. java内存管理机制

    JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 ( ...

  6. 浅析java内存管理机制

    内存管理是计算机编程中的一个重要问题,一般来说,内存管理主要包括内存分配和内存回收两个部分.不同的编程语言有不同的内存管理机制,本文在对比C++和Java语言内存管理机制的不同的基础上,浅析java中 ...

  7. java 内存管理机制

    垃圾收集算法 1.标记清理算法:效率不高(标记和清理过程效率都不高).会形成内存碎片 2.复制算法:把内存分为两部分,当进行回收时,把使用部分的存活对象复制到未使用部分,然后两部分内存角色互换(一个为 ...

  8. Java 内存管理

    java 内存管理机制 JAVA 内存管理总结 java 是如何管理内存的 Java 的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字 new 为 ...

  9. Java内存管理笔记

    java内存管理机制 在java中,内存管理由JVM完全负责,java中的"垃圾回收器"负责自动回收无用对象占据的内存资源,这样可以大大减少程序猿在内存管理上花费的时间,可以更集中 ...

随机推荐

  1. Effective JavaScript Item 38 调用父类的构造函数在子类的构造函数

    作为这一系列Effective JavaScript的读书笔记. 在一个游戏或者图形模拟的应用中.都会有场景(Scene)这一概念.在一个场景中会包括一个对象集合,这些对象被称为角色(Actor). ...

  2. QPointer,QSharedPointer,QWeakPointer的区别与使用例子(QSharedPointer类似Delphi里的引用计数,是强引用,而QWeakPointer是弱引用,不影响原始对象的引用计数,相当于是在暗中观察对象,但保持联系,需要的时候就会出现)

    QPointer is a template class that provides guarded pointers to Qt objects and behaves like a normal ...

  3. 【codeforces】Codeforces Round #277 (Div. 2) 解读

    门户:Codeforces Round #277 (Div. 2) 486A. Calculating Function 裸公式= = #include <cstdio> #include ...

  4. SQL_DML简单的操作

    ***********************************************声明*************************************************** ...

  5. C++ 异常机制分析(C++标准库定义了12种异常,很多大公司的C++编码规范也是明确禁止使用异常的,如google、Qt)

    阅读目录 C++异常机制概述 throw 关键字 异常对象 catch 关键字 栈展开.RAII 异常机制与构造函数 异常机制与析构函数 noexcept修饰符与noexcept操作符 异常处理的性能 ...

  6. 使用 NodeJS + Express从GET/POST Request 取值

    过去无论哪一种网站应用程式的开发语言,初学者教学中第一次会提到的起手式,八九不离十就是GET/POST Request 的取值.但是,在Node.js + Express 的世界中,仿佛人人是高手,天 ...

  7. 简历上的哪些内容才是 HR 眼中的干货?

    资源业绩能力知识自评 https://www.zhihu.com/question/39722495/answer/199846861

  8. Qt SQLite 批量插入优化(SQLite默认将每条语句看成单独的事务)good

    使用SQLite存储数据时发现插入速度太慢,程序跑了将近五分钟才插入了不到三千条.上网查资料才发现,SQLite这种文件数据库与MySql机制不一样,每条事务都有打开和关闭文件的步骤,SQLite默认 ...

  9. delphi中使用词霸2005的动态库XdictGrb.dll实现屏幕取词

    近日来,在网上发现关于屏幕取词技术的捷径,搜索很长时间,发现实现方式以VB出现的居多,但是通过Delphi来实现的却好象没有看到,自己参考着VB的相关代码琢磨了一下通过delphi来实现的方式. 其实 ...

  10. C语言实现的CRC16/CCITT-FALSE校验码函数

    要求:输入字符串“00 AA FF CC AA 01 00” 得到校验码“79B1” 方法1: // ConsoleApplication1.cpp: 定义控制台应用程序的入口点. // #inclu ...