5.JVM内存管理
JAVA虚拟机在执行java程序的过程中,会把它管理的内存分成若干个不同的数据区域。
------------------------------------------------------------------------------------—
| 运行时数据区 |
| ----------- -------- ----------------- |
| | 方法区 | | 栈 | | 本地方法栈 | |
| | | | | | | |
| ----------- -------- ----------------- |
| |
| --------------------------- ----------------- |
| | 堆 | | 程序计数器 | |
| | | | | |
| --------------------------- ----------------- |
| ⬇️ ⬆️ ⬇️ ⬆️ |
| ---------------------------- ------------------ ----------------- |
| | 执行引擎 | | 本地库接口 | ➡️ | 本地方法库 | |
| | | | | | | |
| ---------------------------- ------------------ ----------------— |
|
| 其中,堆和方法区,是所有线程共有区;
| 栈,本地方法栈,程序计数器,是线程私有区。
|------------------------------------------------------------------------------------
(1) 内存区域
a.程序计数器(Program Counter Register),线程私有,不会抛出任何内存异常
I.可以这么理解,当前线程所执行字节码的行号指示器。字节码解释器,就是通过程序计数器的值,来选取下一条要执行的字节码指令(分支、循环、跳转等基础功能都需要依赖计数器)。
II.java虚拟机的多线程是通过,各个线程之间轮流切换并分配内存来实现的。在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换回来之后能够
恢复到正确的执行位置。每条线程都需要一个独立的程序计数器。
III.如果线程正在执行的是一个java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是native方法,计数器的值为空。

b.java虚拟机栈 (Java Virtual Machine Stack) , 线程私有,会有 StackOverFlow 和 OutOfMemoryError异常 ,通过-Xss分配内存大小
I.每个方法在执行时,都会创建一个栈帧(Stack Frame),用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
每一个方法从调用到执行完成的过程,就对应一个 栈帧 在虚拟机栈中,入栈到出栈的过程。

II.栈中的局部变量表,所需的内存空间,在编译器间完成分配。在进入一个方法时,这个方法需要在帧(Stack Frame) 中分配多大的内存空间是确定的,在这个方法运行期间,
不会 改变 局部变量表 所占用 内存空间 的大小。

III.当java启动一个线程时,虚拟机会计算出这个线程所需要的栈深度,(比如10),当线程请求的栈深度(每调一个方法压一个栈帧,用掉一个栈深度),大于虚拟机给Stack分配的
栈深度,会抛出StackOverFlow异常。(用javap javap -verbose Test 查看程序的字节码,Code 属性, stack=2 , 可以查看运行的详细过程,包括栈深度,和每个栈帧需要多少个slot)
当一个可扩展栈(栈有可扩展的有固定长度的,由使用的JAVA虚拟决定的),动态扩展时,无法请求到足够的内存(比如我需要10M内存,但是JVM只给我5M),会抛出,
OutOfMemoryError异常。

c.本地方法栈 (Native Method Stack) ,线程私有,会有 StackOverFlow 和 OutOfMemoryError异常,通过-Xss分配内存大小
I.和java虚拟机栈基本一样。区别不过是,
JVM Stack 为 虚拟机 执行java方法 服务;
Native Method Stack 为 虚拟机 执行本地方法服务
II.也会抛出StackOverFlow 和 OutOfMemoryError异常

d.java堆 , 线程共享 , 会抛出OutOfMemoryError异常。,通过-Xms分配内存最小值,-Xmx分配内存最大值
I.存放 对象实例 和 数组。
II.是垃圾回收的主要区域。
III. java堆,可以处于物理上的不连续空间,逻辑上连续即可。可动态扩展,通过-Xms控制大小。
IV.如果堆中没有完成内存分配,并且堆也无法扩展是,将会抛出OutOfMemoryError异常。

e.方法区 , 线程共享 , 会抛出OutOfMemoryError异常。
I.用于存储,已经被虚拟机加载的,类的信息、常量、静态变量、编译后的代码等数据
II.不需要连续的内存,可以选择固定大小和可扩展
III.当方法区无法完成内存分配需求时,会抛出OutOfMemoryError异常。
e-slave. 运行时常量池,会抛出OutOfMemoryError异常。
是方法区的一部分。
I.Class文件中,除了有类的 版本、 字段、方法、接口、等描述信息外,还有一项就是常量池(Constant Pool Table),用于存放编译期生成的,
各种字面变量和符号引用(),这部分将在类加载后进入方法区的常量池。
II.并非预置在Class文件中常量池中的内容,才能进入方法区;运行期间也可能将新的常量放入池中。

f.直接内存,并不是java虚拟机内存的一部分,而是机器内存的一部分,会抛出OutOfMemoryError异常
I.NIO引入了一种类似于 通道(Channel) 和 缓冲区(Buffer) ,可以使用Native函数库直接分配堆外内存。
然后,通过一个存储在java堆中的,DirectByteBuffer对象作为这块内存的引用进行操作。
这样避免了在java堆和native堆中来回复制数据,提高了性能。
II.当直接内存和JVM内存之和大于机器内存时,抛出OutOfMemoryError内存。

(2)对象创建细节
a.内存分配方式
I.指针碰撞, 如果java堆中内存是绝对规整的,为对象分配空间的任务,等同于把一块确定大小的内存从java堆中划分出来。
如果java堆中内存是绝对规整的,所有用过的内存放在一边,空闲的内存放在另一边,中间放着一个指针作为临界点,那么分配内存就是,
把指针向空闲空间那边挪动等同于对象大小的距离,这种分配方式成为 指针碰撞。

II.空闲列表, 如果内存是不规则的,虚拟机就必须维护一个表,记录那些内存块是可用的,分配的时候找到一块足够大的内存块分给对象实例,
并更新列表的记录,这种方式称为 空闲列表(Free List)

b.选择哪种分配方式是java堆是否规整决定的,java堆是否规整,是由采用的垃圾收集器是否带有压缩功能决定的。因此,
使用Serial、ParNew灯光带有压缩(Compact)过程的收集器时,系统采用的分配算法是指针碰撞。
使用CMS这种基于 Mark-Sweep(标记-移除) 算法的收集器,系统采用的分配算法是空闲列表。

c.空闲列表问题,及解决方案
问题:
首先,堆是线程共有的,所以,当多线程创建对象是,有这样一个问题,当Thread A分配一块内存完成后,还没更新列表,这时Thread B给
自己的对象分配了同一块内存,这就造成了冲突。
方案:
I.对分配内存的动作,进行同步,这种造成性能下降。
II.每个线程在堆中,预先分配一小块内存作为缓冲区,称为(Thread Local Allocation Buffer , TLAB) ,哪个线程需要给自己的对象分配
内存,就在自己的TLAB上分配,只有自己的TLAB上分配完了,才需要同步锁定。通过-XX:+/-UseTLAB参数来设定。(性能调优)

d.内存分配完成后,虚拟机将分配到的内存空间初始化为零值。这一步操作保证了Java代码中可以不赋初始值就可以使用

e.接下来,虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、对象的hash-code、对象的GC分代年龄,等信息。存在对象头中。
这样从JVM的角度来说,对象创建完成。

f.对象的内存布局,对象在内存中的存储可以分为3块区域:
I.对象头 (Header) : 包括两部分信息,
第一部分,存储对象自身运行时数据,如HashCode,GC分代年龄,锁定状态标志,线程持有的锁。
第二部分,类型指针,即对象指向它的 类 元数据的指针,虚拟机通常用这个指针确定对象属于哪个类。
还有记录数组长度的信息。
II.实例数据 (Instance Data) : 程序定义的个字段的内容。包括父类和子类。
III.对齐填充 (Padding) : 占位符,换句话说,就是保证对象大小必须是 8字节(byte) 的整数倍

g.对象的访问定位
java程序需要使用栈上面的reference,引用数据来操作堆上的具体对象。
I. 句柄
这种方式,Java堆中会分配一块内存,作为句柄池,reference存储的就是对象句柄池地址。
句柄中包含了对象实例数据 (在堆上),和类型数据(类数据,在常量池)具体地址信息。
好处:GC后reference不需要修改
II.直接指针
reference存储的就是对象地址。
好处:速度快,节省了一次指针定位开销。
Sun HotSpot使用直接指针
(3)堆溢出,OutOfMemoryError 后面跟 Heap
-Xms 和 -Xmx 设置堆的最大和最小内存
堆的最小参数 -Xms 和 最大参数 -Xmx 设置为一样,就可以避免堆扩展。
a.解决思路:
I.用内存映像分析工具(如,Eclipse Memory Analyzer) 堆Dump出来的堆转储快照进行分析。
II.分析的重点是确认内存中的对象是否是必要的,即先确认是否有 内存泄漏(Memory Leak,当创建
的对象没有使用,又无法被GC回收,就是内存泄漏)
III.如果是内存泄漏,查看泄漏对象到GC Roots的引用链信息,就能找到泄漏对象是通过怎样的路径与GC Roots相关联,
并导致GC无法自动回收他们的。通过引用链信息,定位到泄漏代码的位置,review代码。
IV.如果没有内存泄漏,即,内存中的对象都必须存活。那就看虚拟机堆参数(-Xms和-Mmx)和内存相比,看是否还可以调大。
从代码上检查是否有,某些对象生命周期过长,持有时间过长的情况,尝试优化这些代码,从而减少运行期的内存消耗。
(4)栈溢出 StackOverFlow
-Xss设置栈占用内存大小。默认1024K,也就是1M
a.虚拟机启动时,有栈大小的默认参数,当所有的栈帧(Stack Frame),内存加起来超过栈内存大小时,就会抛出StackOverFlow异常。
在栈深度,默认情况下,大多数栈深度达到1000-2000帧没有问题,对于普通递归是够用了(但是栈帧大小是不确定的,所以,只能是大多数情况下。)
b.建立线程数量过多,导致内存溢出
I.操作系统,分配给每个进程的内存是有限制的。如果给一个java分配了1G内存,
虚拟机提供了参数,来控制堆和方法区所占用内存大小,如果没有指定栈占用的内存大小,忽略其它,剩余的内存 1G - 堆内存 - 方法区内存,被本地方法栈和虚拟机栈
瓜分,栈是线程私有的,栈分配的内存越大,可以建立的线程数就越少,建立新线程时候,容易把剩下的内存耗尽。这种情况,可以减少最大堆,和减少栈容量,换取更多
的线程,避免内存溢出。
(5)方法区,内存溢出 。OutOfMemoryError后面跟随PermGen
-XX:PerSize 和 -XX:MaxPermSize限制方法区大小
String.intern()是一个native方法,作用:如果字符串常量池中,已经包含一个等于此String 对象的字符串,则返回常量池中,代表此字符串的对象。
否则,将此String对象添加到常量池中。
a.Spring Hibernate在对类进行增强时,都会使用到CGLib这类字节码技术,增强的类越多,就需要越大的方法区,容易导致方法区的内存溢出。
b.JSP第一次运行时,要编译成java类,大量的jsp也有可能导致方法区内存溢出。

(6) 本机直接内存溢出 OutOfMemoryError Unsafe.allocateMemory
DirectMemory 容量可以通过:-XX:MaxDirectMemorySize指定,如果不指定,则默认与java堆最大值 (-Xmx)一样。
如果内存溢出,在堆的Dump文件很小,或者没有明显的异常,又或者程序中使用了NIO,可以考虑是 本机直接内存溢出。

转载于:https://www.cnblogs.com/fubaizhaizhuren/p/5938480.html

jvm--3.内存管理相关推荐

  1. Java虚拟机JVM的内存管理

    Java虚拟机JVM的内存管理 关键词 一.JVM整体架构 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 名称 作用 特征 配置参数 异常 程序计数器 ...

  2. 解析Java对象引用与JVM自动内存管理(2)

    解析Java对象引用与JVM自动内存管理(2) 作者:杨扬 本文选自:赛迪网 2002年11月22日 Soft References 应用实例 下面以在基于web的应用程序中使用soft refere ...

  3. JVM的内存管理机制详解

    一.为什么要学习内存管理? Java与C++之间有一堵由内存动态分配和垃圾回收机制所围成的高墙,墙外面的人想进去,墙里面的人出不来 对于Java程序员来说,JVM给我们提供了自动内存管理机制,不需要既 ...

  4. 第五篇:初识JVM,JVM自动内存管理

    文章目录 一.前言 1.1 计算机==>操作系统==>JVM 1.1.1 虚拟与实体(对上图的结构层次分析) 1.1.2 Java程序执行(对上图的箭头流程分析) 二.JVM内存空间与参数 ...

  5. 一、JAVA虚拟机------JVM自动内存管理

    JVM自动内存管理 一.JAVA内存区与内存溢出 1.1 概述 1.2 运行时数据区 1.2.1 程序计数器 (Program Counter Register) 1.2.2 Java虚拟机栈(Jav ...

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

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

  7. JVM的内存管理 Ⅰ

    最近在看Java优化这方面的书,那必然会涉及到内存管理,我想和大家共同提高,于是写出来,如果有问题,希望能提出来,共同进步嘛!呵呵,不说这些了,好,现在开始你的知识之旅吧! 在做Java开发的时候常用 ...

  8. 读书笔记——深入理解JVM(JVM自动内存管理)

    简介 本系列为<深入理解Java虚拟机-JVM高级特性与最佳实践>一书的阅读笔记. 本书开头介绍了JVM发展的历史,接着介绍了JVM是如何实现自动内存管理的. 本章节主要介绍: JVM的存 ...

  9. JVM 自动内存管理

    JVM Java 内存区域 虚拟机栈 栈帧的组成 动态分派 栈可能会引发的问题 基于栈的解释执行 本地方法栈 程序计数器 堆 分区 内存分配机制 方法区 元空间与永久区 运行时常量池 直接内存(堆外内 ...

  10. Java JVM与内存管理

    1.JVM内存区域划分(程序计数器.java堆.java虚拟机栈.本地方法栈.方法区) 程序计数器 一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器. 如果线程正在执行的是一个 ...

最新文章

  1. 图片的宽铺满全屏 微信小程序_2020冬至快乐图片!,图片祝福贺卡,朋友圈冬至早安问候语动态图片微信小程序...
  2. 构造函数调用虚函数的问题
  3. 简明深度学习方法概述 Deep Learning:Methods and Application
  4. UA OPTI501 电磁波 求解Maxwell方程组的波动方程方法
  5. php对手时间戳判断,PHP 中判断一个数字是否是Unix 时间戳
  6. java内部类为什么会持有外部类的引用_java中内部类为什么会持有外部类的引用?...
  7. 为什么volatile不能保证原子性而Atomic可以?
  8. ActiveMQ_基础学习
  9. python基础公式_一、Python基础(数据类型、基本函数、基本运算)
  10. php stream encoding,PHP之mb_check_encoding使用方法分享
  11. easyui datalist 不显示数据_爬虫练习——豆瓣电影信息爬取及数据可视化
  12. 浅谈css常用伪类用法
  13. Android MTK TP Driver 触屏驱动
  14. 差分进化算法求解函数最优解matlab实现
  15. JavaScript(JS)的简单了解
  16. 关于write和read以及文件读写位置
  17. switch 语句 -- 超详解
  18. excelDownload
  19. 组合学:使用10个数字与52个字母生成1477万个不重复的4位串码V4衍生版本
  20. 微信聊天机器人搭建方式分享

热门文章

  1. 【软件设计师】2020-08-07
  2. python字母大小写排序_Python中sorted()排序与字母大小写的问题
  3. python删除列表元素_追求简单C++之删除STL列表的元素
  4. Guice进阶之整合mybatis和druid
  5. [机器学习] XGBoost on Spark 分布式使用完全手册
  6. 使用Java中的Try-With-Resource
  7. 【python 7】OpenCV
  8. Jacobi并行拆解
  9. 银监会周末突发4号文件,18年贷款比想象的要难
  10. android 开启wifi失败,[求助]获取基站/wifi信息为空或失败