《深入理解Java虚拟机》读书笔记2

  第2章Java内存区域与内存溢出异常

2.2运行时数据区域


1. 程序计数器 是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器完成。此内存区域是唯一一个在java虚拟机规范中 没有规定任何OutOfMemoryError情况的区域。
a、如果线程正在执行的是一个 Java 方法,计数器记录的是正在执行的虚拟机字节码指令的地址;
b、如果正在执行的是 Native 方法,这个计数器的值为空。

2. Java虚拟机栈
1) 也是线程私有的,它的 生命周期和线程 相同,它描述的是java方法执行的内存模型:每一个方法 被执行 的时候都会同时创建一个 栈帧 用于存储局部变量表、操作栈、动态链接、方法出口等信息。
2) 每一个方法被调用直至执行完成的过程,就对应这一个栈帧在虚拟机栈中从入栈到出栈的过程。
3) 人们通常所说的 栈内存 就是现在讲的虚拟机栈,或者说是虚拟机栈中的 局部变量部分
4) 局部变量所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。
5) 对这个区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverFlowError异常 ;若虚拟机可以动态扩展,当扩展时无法申请到足够的内存会抛出 OutOfMemoryError异常

3. 本地方法栈 与虚拟机栈所发挥作用相似,区别就是本地方法栈是为虚拟机使用到的Native方法服务,而虚拟机栈是为虚拟机执行Java方法服务。同样会抛出StackOverFlowError异常和OutOfMemoryError异常。

4. Java堆(Java Heap)
(1) 对于大多数应用,Java堆是java虚拟机所管理的内存中 最大的一块。
(2) Java堆是被所有线程共享的一块内存区域,在 虚拟机启动时创建
(3)  唯一目的是存放对象实例 ,几乎所有的对象实例都在这里分配内存。
(4)  Java堆是垃圾收集器管理的主要区域,也被称作GC堆 (Garbage Collection Heap)。由于现在收集器基本采用分代收集算法,所以Java堆可以细分为:新生代和老生代;再细致一点有Eden空间、FromSurvivor空间、ToSurvivor空间等。划分目的是为了更好的收回内存,或更快地分配内存。
(5) 若堆中没有内存可以完成实例分配,并且堆也无法扩展时,将会抛出 OutOfMemoryError异常
(6) Java堆可以处于物理上不连续但逻辑上连续的内存空间中。

5. 方法区
1) 方法区与Java堆一样是各个线程共享的内存区域, 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
2) 这个区域的回收主要是 针对常量池的回收和对类型的卸载 。一般来说这个区域的回收“成绩”比较令人满意,尤其是类型的卸载,条件相当苛刻,但这部分区域的回收确实是有必要的。
3) 该区可以选择不实现垃圾收集。
4) 当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError异常。
5) 对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为 永久代, 本质两者并不等价。

6. 运行时常量池
(1) 它是方法区的一部分——Class文件中还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分将在类加载后存放到方法区的运行时常量池中。
(2) 运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性(第一个是Java虚拟机规范没有对运行时常量池的格式做严格的规定)。
(3) 同样可抛出OutOfMemoryError异常。

7. 直接内存 并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但这部分内存也被频繁使用。由于直接内存容易被忽略掉,所以服务器管理员在配置虚拟机参数时,使得各个内存区域的总和大于物理内训限制,从而导致OutOfMemoryError异常。

2.3对象访问

一般来说,一个Java的引用访问涉及到3个内存区域:JVM栈,堆,方法区。
  以最简单的本地变量引用:Object obj = new Object()为例:
  • Object obj表示一个本地引用,存储在JVM栈的本地变量表中,表示一个reference类型数据;
  • new Object()作为实例对象数据存储在堆中;
  • 堆中还记录了Object类的类型信息(接口、方法、field、对象类型等)的地址,这些地址所执行的数据存储在方法区中;
1. 不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种: 使用句柄和直接指针。
2. 使用句柄访问方式,Java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的 句柄地址 ,而句柄中包含了对象实例数据和类型数据各自的具体 地址信息


3. 使用指针访问方式,Java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址。

4. 句柄访问方式的最大好处:reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
5. 直接指针访问方式的最大好处是 速度快 ,节省了一次指针定位的时间开销。

 
2.4实战OutOfMemoryError异常OOM

1. Java堆溢出
① Java堆用于存储对象实例,只要不断创建对象,保证GC Roots到对象之间有 可达路径来避免垃圾回收机制清除这些对象 ,就会在对象数量到达最大堆的容量限制后产生内存溢出异常。
② 抛出异常:Java.lang.OutOfMemorryError: Java heap space
③ 解决该区域异常:通过内存映像分析工具对dump出来的堆转储快照进行分析,确定是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)>  a若内存泄漏,进一步使用工具查看泄漏对象到GC Roots的应用链,准确地定位出泄漏代码的位置;b 若不存在泄漏,就是指内存中的对象确实都还必须存活着,应当检查虚拟机的堆参数( - Xms与 - Xmx ,与机器物理内存对比看是否可以调大,从代码上查看是否存在生命周期过长、持有状态时间过长的情况,尝试减少程序运行时的内存消耗。



2. 虚拟机栈和本地栈方法溢出
① 栈容量只由 -Xss 参数设定
② 关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StakeOverFlowError异常;
若虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。其实两者有重叠。
 在单线程下,无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StakeOverFlowError异常。
④ 对于正常的方法调用(包括递归),深度应该是够用的,但是,若是建立多线程导致的内存溢出,再不能减少线程数或更换64为虚拟机的情况下,就只能通过 减少最大堆和减少栈容量 来换取更多线程。

3. 运行时常量池溢出
① 若要向运行时常量池中添加内容,最简单的做法就是使用 String.intern()这个Native方法
② String.intern()作用:若池中已经包含一个等于此String对象的字符串,则返回代表持中的这个字符串的String对象;否则,将此String对象所包含的字符串添加到常量池中,并且返回此String对象的引用。
③ 常量池分配在方法区, 通过-XX:PermSize和-XX:MaxPermSize限制方法区大小 ,从而间接限制常量池容量。
④ 抛出异常:Java.lang.OutOfMemorryError: PermGen space

4. 方法区溢出
通过-XX:PermSize和-XX:MaxPermSize限制方法区大小
① 方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。(类信息)
② 测试方法:产生大量的类去填满方法区,直至溢出。
③ 比如:GCLib这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成的Class可以加载入内存。
④ 常见的还有:大量的JSP或动态产生JSP文件的应用(JSP第一次运行时需要编译成Java类)、基于OSGIDE应用(即使是同一个java类,在不同的加载器也会视为不同的类)。

5. 本机直接内存溢出
通过: -XX:MaxDirectMemoreySize指定;若不指定,默认与Java堆的最大值(-Xmx)一样
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值