深入理解JVM 第二章 Java内存区域与内存溢出异常

1.运行时数据区域

在这里插入图片描述

1.1 程序计数器

作用: 记住下一条JVM指令的执行地址。字节码解释器工作时就是通过改变这个计数器的值来获取下一条需要执行的字节码指令。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。

特点: 1 线程私有;2 唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

1.2 Java虚拟机栈

定义: 每个线程运行时所需要的内存。每个栈由多个栈帧(Frame)组成,对应着每次方法调用时做占用的内存。每个线程只能有一个活动栈帧,对应着当前正在执行的方法。它的生命周期与线程相同,故是线程私有的。

异常分类: StackOverflowError / OutOfMemoryError

问题辨析:

  1. 垃圾回收是否涉及栈内存? —— 不需要,栈帧自己完成进栈和出栈。
  2. 栈内存分配越大越好吗? ——并不是
  3. 方法内的局部变量是否线程安全?
    如果方法内的局部变量没有逃离方法的作用范围,它就是线程安全的。如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全。

线程运行诊断

  1. 用top命令定位哪个进程对CPU占用过高
  2. 用ps命令进一步定位是哪个线程引起的CPU占用过高(ps H -eo pid,tid,%cpu|grep 进程id)
  3. 根据线程id找到有问题的线程,进一步定位到问题代码的源码行号。(jstack 进程id)

1.3 本地方法栈

本地方法栈式为虚拟机使用到的Native方法服务。有的虚拟机(如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。本地方法栈区域同样会抛出StackOverflowError和OutOfMemoryError异常。

1.4 Java堆

定义: Java是被所有线程共享的一块区域,在==虚拟机启动时创建。==此内存区域的唯一目的就是 ==存放对象实例。==所有的对昂实例都在这分得内存,同时Java堆也是垃圾收集器管理的主要区域。

堆内存诊断
· jps工具——查看当前系统中有哪些java进程
· jmao工具——查看堆内存占用情况
· jconsole工具——图形界面,多功能检测工具,可连续检测

1.5 方法区

定义: 线程共享的内存区域。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

不同JDK版本的方法区的结构
在这里插入图片描述
JDK1.6的方法区在JVM内存中,其实现是永久代(PermGen)。垃圾回收在这个区域比较少出现,主要回收目标是针对常量池的回收和==对类型的卸载。==根据Java虚拟机规范,当方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常。

JDK1.8的方法区在本地内存,其实现是元空间(Metaspace),但字符串常量池放到了堆中。==注意:运行时常量池还在方法区。==这样类的元数据分配只受本地内存大小的限制,OOM问题就不存在了。

运行时常量池
定义: 方法区的一部分,包含于Class文件中,用于存放编译器生成的各种字面量和符号引用,Class中还有类的版本、字段、方法、接口等描述信息,这部分内容在类加载后存在到方法区的运行时常量池中。运行时常量池仙姑低于Class文件常量池的一个重要特性就是动态性,Java并不要求常量一定只能在编译器产生,运行期间可能将心得常量放入池中,这总特性被开发人员用的较多的就是String类的intern()方法

StringTable特性:

  1. 常量池中的字符串仅是符号,第一次用到时才变为对象。
  2. 利用串池机制可避免重复创建字符串对象。
  3. 字符串变量的拼接原理是StringBuilder(JDK 1.8)。
  4. 字符串常量拼接的原理是编译器优化。
  5. 可以使用intern方法主动将串池中还没有的字符串对象放出串池。
    (a) JDK 1.6将这个字符串对象尝试放入串池,如果池中已有则不会放入;如果池中没有则会把此对象复制一份,放入串池,会把串池中的对象返回。
    (b) JDK 1.8将这个字符串对象尝试放入串池,如果池中已有则不会放入;如果没有则放入串池,会把串池中的对象返回。

StringTable性能调优

  1. 调整 -XX:StringTableSize=桶个数
  2. 考虑将字符串对象入池,避免重复。

测试
在这里插入图片描述
在这里插入图片描述

1.6 直接内存

定义: 常见于NIO操作时,用于数据缓冲区。分配回收成本较高,但读写性能高。不受JVM内存回收管理。

JDK 1.4中新加入了NIO(New Input/Output)类,引入了基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里边得DirectByteBuffer对象作为这块内存得引用进行操作。这样能在一些场景显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
在这里插入图片描述
分配和回收原理
· 使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法。
· ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存。
在这里插入图片描述

2.对象访问

reference类型在Java虚拟机规范里边只规定了一个指向对象的引用,并没有定义这个引用应该通过那种方式去定位,主流的访问方式有两种:使用句柄直接指针。
1.如果使用句柄访问方式,Java堆中将划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自具体的地址信息。
在这里插入图片描述
2.如果使用直接支真访问凡是,Java堆对象的布局中必须考虑如何防止访问类型数据的相关信息,reference中直接存储的就是对象的地址。
在这里插入图片描述
这两种对象的访问方式各有优势,使用句柄方式的好处是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动兑现非常普遍)时指挥改变句柄中实例数据指针,而reference不需要被需改。使用直接指针访问方式好处是速度更快,它节省了依次指针定位的时间开销。Sun HotSpot使用第二种方式进行对象访问。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值