jvm--3.内存管理
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.内存管理的更多相关文章
- jvm的内存管理【转】
[转]JVM内存管理 这些日子一直在研究jvm内存管理的东西,网上的知识很多,总结一下,能沉淀下来的就是自己的! 首先,刚学java的时候就知道java类文件是以 .java为后缀的文件,经过java ...
- JVM的内存管理机制-转载
JVM的内存管理机制 一.JVM的内存区域 对于C.C++程序员来说,在内存管理领域,他们既拥有每一个对象的"所有权",又担负着每一个对象生命开始到终结的维护责任. 对Java程序 ...
- JVM的内存管理机制
在做Java开发的时候常用的JVM内存管理有两种,一种是堆内存,一种是栈内存.堆内存主要用来存储程序在运行时创建或实例化的对象与变量,例如:我们通过new MyClass()创建的类MyClass的对 ...
- JVM自动内存管理学习笔记
对于使用 C.C++ 的程序员来说,在内存管理领域,他们既是拥有最高权力的皇帝又是从事最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任.对于 Java 程 ...
- JVM自动内存管理-Java内存区域与内存溢出异常
摘要: JVM内存的划分,导致内存溢出异常的可能区域. 1. JVM运行时内存区域 JVM在执行Java程序的过程中会把它所管理的内存划分为以下几个区域: 1.1 程序计数器 程序计数器是一块较小的内 ...
- JVM自动内存管理机制——Java内存区域(下)
一.虚拟机参数配置 在上一篇<Java自动内存管理机制——Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...
- JVM自动内存管理机制——Java内存区域(上)
一.JVM运行时数据区域概述 Java相比较于C/C++的一个特点就是,在虚拟机自动内存管理机制的帮助下,我们不需要为每一个操作都写像C/C++一样的delete/free代码,所以也不容易出现内存泄 ...
- JVM自动内存管理机制--读这篇就GO了
之前看过JVM的相关知识,当时没有留下任何学习成果物,有些遗憾.这次重新复习了下,并通过博客来做下笔记(只能记录一部分,因为写博客真的很花时间),也给其他同行一些知识分享. Java自动内存管理机制包 ...
- JVM自动内存管理:内存区域基础概念
1.课程概要 (1)Java虚拟机和Java内存区域概述 (2)Java虚拟机栈和本地方法栈 (3)Java堆 (4)方法区和运行时常量池 (5)直接内存 2.Java虚拟机运行时数据区 运行时数据区 ...
- JVM堆内存管理与自定义分配参数详解
堆内存模型: 在Java中,堆被划分成两个不同的区域:新生代(Young),老年代(Old).而Permanent属于永久代(方法区),不属于堆内存.新生代又被分为了三个区域:Eden,from s ...
随机推荐
- python学习1
1.由于win8的电脑上出现了0xc0000b错误不能解决,所以现在使用的是虚拟机中的Linux系统.安装过程见http://blog.csdn.net/yuzhongchun/article/det ...
- STL基础
vector: 1.头文件#include<vector> 2.声明vector对象,vector<int> vec; 3.尾部插入a:vec.push_back(a); 4. ...
- oracle add_months函数
oracle add_months函数 add_months 函数主要是对日期函数进行操作,举例子进行说明 add_months 有两个参数,第一个参数是日期,第二个参数是对日期进行加减的数字(以月为 ...
- MVC 数据验证
MVC 数据验证 前一篇说了MVC数据验证的例子,这次来详细说说各种各样的验证注解.System.ComponentModel.DataAnnotations 一.基础特性 一.Required 必填 ...
- 如何查看当前Ubuntu系统的版本
如何查看当前Ubuntu系统的版本 说来也惭愧,用Ubuntu差不多快1个月了,双系统是让朋友安的,只知道自己使用的是什么12版本的,具体怎么看还不知道,下面写一下查看当前Linux系统的版本的方法 ...
- 实体类和DataTable的转换
引子 最近在项目中在数据库查询的时间,总是要用到数据表到实体类对象列表的转化,自己封装了一个转换的方法,用起来还比较方便,记下来,以后可以重复使用,原理就主要是利用反射,当然有更好的ORM框架可以实现 ...
- iOS10推送通知适配
iOS10推送新增了UserNotifications Framework,使用起来其实很简单. 只是在iOS10以上系统上点击通知栏,回调方法不再走原来的这两个方法 - (void)applicat ...
- win10使用技巧之如何打出偏僻字母
一.背景 有时需要在打出一些希腊字母,诸如ɛ.μ等字符,如果输入法不支持该怎么办呢?在很多国产拼音软件中,都会提供扩展方便用户寻找这类字符,但是如果用户换过一款软件,可能要在一定时间找到这些字符就没那 ...
- Spring Boot
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过 ...
- 精通Web Analytics 2.0 (10) 第八章:竞争情报分析
精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第八章:竞争情报分析 在现实世界中,收集竞争情报可能意味着雇人在竞争对手的垃圾桶(实际会发生!)翻找. 在虚拟世界中,堆如山的数 ...