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,可以考虑是 本机直接内存溢出。

jvm--3.内存管理的更多相关文章

  1. jvm的内存管理【转】

    [转]JVM内存管理 这些日子一直在研究jvm内存管理的东西,网上的知识很多,总结一下,能沉淀下来的就是自己的! 首先,刚学java的时候就知道java类文件是以 .java为后缀的文件,经过java ...

  2. JVM的内存管理机制-转载

    JVM的内存管理机制 一.JVM的内存区域 对于C.C++程序员来说,在内存管理领域,他们既拥有每一个对象的"所有权",又担负着每一个对象生命开始到终结的维护责任. 对Java程序 ...

  3. JVM的内存管理机制

    在做Java开发的时候常用的JVM内存管理有两种,一种是堆内存,一种是栈内存.堆内存主要用来存储程序在运行时创建或实例化的对象与变量,例如:我们通过new MyClass()创建的类MyClass的对 ...

  4. JVM自动内存管理学习笔记

    对于使用 C.C++ 的程序员来说,在内存管理领域,他们既是拥有最高权力的皇帝又是从事最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任.对于 Java 程 ...

  5. JVM自动内存管理-Java内存区域与内存溢出异常

    摘要: JVM内存的划分,导致内存溢出异常的可能区域. 1. JVM运行时内存区域 JVM在执行Java程序的过程中会把它所管理的内存划分为以下几个区域: 1.1 程序计数器 程序计数器是一块较小的内 ...

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

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

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

    一.JVM运行时数据区域概述 Java相比较于C/C++的一个特点就是,在虚拟机自动内存管理机制的帮助下,我们不需要为每一个操作都写像C/C++一样的delete/free代码,所以也不容易出现内存泄 ...

  8. JVM自动内存管理机制--读这篇就GO了

    之前看过JVM的相关知识,当时没有留下任何学习成果物,有些遗憾.这次重新复习了下,并通过博客来做下笔记(只能记录一部分,因为写博客真的很花时间),也给其他同行一些知识分享. Java自动内存管理机制包 ...

  9. JVM自动内存管理:内存区域基础概念

    1.课程概要 (1)Java虚拟机和Java内存区域概述 (2)Java虚拟机栈和本地方法栈 (3)Java堆 (4)方法区和运行时常量池 (5)直接内存 2.Java虚拟机运行时数据区 运行时数据区 ...

  10. JVM堆内存管理与自定义分配参数详解

    堆内存模型: 在Java中,堆被划分成两个不同的区域:新生代(Young),老年代(Old).而Permanent属于永久代(方法区),不属于堆内存.新生代又被分为了三个区域:Eden,from  s ...

随机推荐

  1. MongoDB查询转对象是出错Element '_id' does not match any field or property of class

    MongoDB查询转对象是出错Element '_id' does not match any field or property of class   解决方法: 1.在实体类加:[BsonIgno ...

  2. [LeetCode] Reconstruct Itinerary 重建行程单

    Given a list of airline tickets represented by pairs of departure and arrival airports [from, to], r ...

  3. HFSS仿真小例

    微带拐角对比实验 微带四分之波长变换器插入前后对比 P1 P2 分配波端口激励,设置差分信号线 微带线差分对的差模阻抗随着差分线间距的增大而增大,随着线宽的增大而减小: 共模阻抗随着差分线间距的增大而 ...

  4. 一个简单oop的changeTab

    好多地方都会用到这样一个效果“点击tab切换内容页”,根据自己的想法实现了一下,写了个简单的插件.以前写代码都是标准的函数式编程,现在觉得面向对象编程看起来比较爽,并且更容易维护,于是就用oop的思想 ...

  5. vs2013中2.0类库提示是英文,解决方案

     将C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\zh-Hans下的XML文件复 ...

  6. 搭建web框架手册(一)

    昨天听完永康对EASYUI的介绍后终于明白了优秀的UI框架就是第一生产力,过去自己一直沉浸在后端代码中,完全忽视了前端的生产力交互,总觉得界面漂亮就是生产力,其实大错特错,真正的具有高效生产力的界面其 ...

  7. CSS Icon 项目地址 小图标-用css写成的

    http://cssicon.space/#/icon/focus 这是所有用css写成的  小图标  右侧有 html和css代码

  8. Django初识

    web框架 Web应用框架有助于减轻网页开发时共通性活动的工作负荷,例如许多框架提供数据库访问接口.标准样板以及会话管理等,可提升代码的可再用性.简单地说,就是你用别人搭建好的舞台来做表演,用别人做好 ...

  9. CVE-2016-1240 Tomcat 服务本地提权漏洞

    catalogue . 漏洞背景 . 影响范围 . 漏洞原理 . 漏洞PoC . 修复方案 1. 漏洞背景 Tomcat是个运行在Apache上的应用服务器,支持运行Servlet/JSP应用程序的容 ...

  10. 02. Let & Const

    Let & Const let 基础用法 很简单就能说明这个问题 if(false) { var a = 'heihei' } a = undefined if(true) { var a = ...