JVM读书笔记(一):Java内存区域与内存溢出异常

来源:互联网 发布:2017年适合做淘宝客吗 编辑:程序博客网 时间:2024/05/21 07:04

一、运行时数据区域

线程共享:方法区、堆
线程隔离:虚拟机栈、本地方法栈、程序计数器

1、程序计数器:当前线程所执行的字节码行号指示器

(Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,在任何一个确定的时刻,一个处理器核都只会执行一个线程中的指令)

若线程执行的是Java方法,计数器记录正在执行的虚拟机字节码指令的地址
若执行的是native方法,计数器值为空

程序计数器是唯一一个没有规定任何OOM情况的区域

2、Java虚拟机栈:描述Java方法执行的内存模型

每个方法在执行的同时都会创建一个栈帧用于存储: 局部变量表、操作数栈、动态链接、方法出口等信息

每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程

局部变量表:存放编译器已知的各种基本数据类型、对象引用、returnAddress(指向一条字节码指令的地址)
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小

java虚拟机栈规定两种异常:

StackOverFlow:线程请求的栈深度大于虚拟机所允许的深度
OutOfMemory:虚拟机栈动态扩展时无法申请到足够的内存

???疑问:如果编译时就已经确定,为什么会动态扩展

3、本地方法栈:与java虚拟机栈功能类似,不过是为native方法提供服务

4、java堆:被所有线程共享

在虚拟机启动时创建:目的是存放对象实例,几乎所有的对象实例都在这里分配内存

可以通过-Xmx和-Xms来控制大小的扩展

OutOfMemory:对中没有内存完成实例分配,并且堆也无法再扩展时

5、方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

OutOfMemory:方法区无法满足内存分配需求时

(运行时常量池):方法区的一部分,用于存放编译期生成的各种字面量和符号引用以及翻译出来的直接引用
区别于Class文件的常量池:运行时常量池具备一定的动态性:运行期间也能放入新的常量:(String 的intern方法)

附、直接内存(不属于虚拟机定义的内存区域,配置虚拟机参数时容易被忽略,导致物理内存不够从而OOM)


二、对象探秘

1、对象的创建

获取new指令后:

检查指令参数能否在常量池中定义到一个类的符号引用,并检查这个引用符号代表的类是否已被加载、解析和初始化过,如果没有,必须先执行相应的类加载过程

类加载检查通过后,为新对象分配内存

GC的压缩整理功能------->Java堆的规整性 :规整    :指针碰撞不规整:空闲列表分配操作原子性保证:CAS+失败重试   or  本地线程分配缓冲(TLAB)

默认零值初始化

各种必要信息的设置

2、对象的内存布局

三块区域:对象头、实例数据、对其填充

对象头:包含两部分信息:1、存储对象自身的运行时数据:HashCode、GC分代年龄、锁状态标志、线程持有的锁等
2、类型指针:虚拟机通过这个指针来确定这个对象是哪个类的实例
附:如果对象是一个Java数组,那么对象头中必须拥有一块记录数组长度的数据,因为虚拟机可以通过普通java对象的元数据信息确定java对象的大小,但是从数组的元数据中却无法确定数组的大小

实例数据:对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容
分配时,相同宽度的字段总是分配到一起,在这个前提下父类中定义的变量会出现在子类之前

对齐填充:占位符:补全

3、对象的访问定位

主要两种方式:句柄 和 直接指针

使用句柄访问:java堆中会划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中则对象实例数据(堆)与类型数据(方法区)各自的具体地址信息。

使用直接指针访问:reference中保存对象的实例数据地址,而对象的类型数据放在实例数据中

优势:
句柄:reference中存储的是稳定的句柄地址,在对象呗移动时只会改变句柄中的实例数据指针,reference本身不需要修改
直接指针:速度快,节省了一次指针定位的时间开销

实战

java堆OOM

-Xms -Xmx -XX:+HeapDumpOnOutOfMemoryError

拿到Dump首先分析是内存泄漏还是内存溢出

内存泄漏:查看泄漏对象到GC ROOTS的引用链。找到泄漏对象是通过怎样的路径与GC ROOTS相关联并导致垃圾回收器无法自动回收他们的。

内存溢出:(对象确实必须活着),此时检查虚拟机的堆参数(-Xmx和-Xms)与机器物理内存相比是否可以调大。检查代码是否存在某些对象生命周期过长、持有状态时间过长的情况。

虚拟机栈OOM

-Xss

单个线程下,无论由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,都抛出StackOverFlow异常

知识点:

通过不断建立线程的方式可以产生OOM,但这跟栈空间不够大无关。为每个栈分配的内存越大,反而越容易产生内存溢出异常原因:操作系统分配给每个进程的内存是有限制的。每个线程分配的栈容量越大,可以建立的线程数量就越少,建立线程时就容易把内存耗尽(通过减少最大堆和栈容量来换取更多线程)

方法区和运行时常量池OOM

-XX:PermSize=… -XX:MaxPermSize=…

jdk1.6和1.7的区别

直接内存OOM

-XX:MaxDirectMemorySize=…

如果不指定,默认和java堆最大值相同(-Xmx)

0 0
原创粉丝点击