深入理解Java虚拟机 - 第二部分 内存管理机制

文章目录

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

2.1 概述

虚拟机自动内存管理机制

2.2 运行时数据区域

2.2.1 程序计数器

当前线程所执行的字节码的行号指示器

2.2.2 Java虚拟机栈

Java方法执行的内存模型

2.2.3 本地方法栈

虚拟机使用的Native方法服务

2.2.4 Java堆

存放对象实例
GC堆,Carbage Collected Heap

2.2.5 方法区

存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据
Permanent Generation 永久代

2.2.6 运行时常量池

Runtime Constant Pool
存放编译期生成的各种字面量和符合引用

2.2.7 直接内存

NIO(New Input/Output)类
一种基于通道(Channel)与缓冲区(Buffer)的I/O方式
使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作

2.3 HotSpot虚拟机对象探秘

2.3.1 对象的创建

  • Bump the Pointer 指针碰撞 Serial、ParNew等带Compact过程的收集器
  • Free List 空闲列表 CMS基于Mark-Sweep算法得收集器
    并发情况下
  • 对分配内存空间的动作进行同步处理
  • 把内存分配的动作按照线程划分在不同的空间中进行
    Thread Local Allocation Buffer,TLAB 本地线程分配缓冲
    Object Header 对象头

2.3.2 对象的内存布局

HotSpot虚拟机中

  • Header 对象头
    • 用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳,官方称为Mark World
    • 类型指针,对象指向它的类元数据的指针
  • Instance Data 实例数据
    对象真正存储的有效信息
    FieldAllocationStyle 虚拟机分配策略参数和字段在Java源码中定义的顺序的影响
  • Padding 对齐填充
    占位符的作用
    2.3.3 对象的访问定位
  • 使用句柄
    Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
  • 直接指针访问
    Java堆对象的布局中必须考虑如何放置访问类型数据的相关信息,而reference中处处的直接就是对象地址

2.4 实战:OutOfMemoryError异常

OutOfMemoryError, OOM

  • 通过代码验证Java虚拟机规范中描述的各个运行时区域存储的内容
  • 遇到实际的内存溢出异常时,能根据异常信息判断是哪个区域的内存溢出,知道什么样的代码可能会导致这些区域内存溢出,以及出现这些异常后该如何处理

2.4.1 Java堆溢出

异常堆栈信息“java.lang.OutOfMemoryError”会跟着进一步提示“Java heap space”
在这里插入图片描述

import java.util.ArrayList;
import java.util.List;

/**
 * VM Args:-Xms20m-Xmx20m-
 XX:+HeapDumpOnOutOfMemoryError
 * @author zzm
 */
public class HeapOOM {
    static class OOMObject{
    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true){
            list.add(new OOMObject());
        }
    }
}

在这里插入图片描述

2.4.2 虚拟机栈和本地方法栈溢出

  • StackOverflowError
  • OutOfMemoryError

2.4.3 方法区和运行时常量池溢出

String.intern()是一个Native方法,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则将此String对象包含字符串添加到常量池中,并且返回此String对象的引用。

第三章 垃圾收集器与内存分配策略

Java与C++之间:内存动态分配和垃圾收集技术

3.1 概述

Garbage Collection, GC 垃圾收集

3.2 对象已死吗

确定对象“存活”、“死去”

3.2.1 引用计数算法

Reference Counting
COM(Component Object Model)技术、使用ActionScript 3的FlashPlayer、Python语言和在游戏领域广泛应用的Squirrel

3.2.2 可达性分析算法

Reachability Analysis
Java、C#、Lisp
GC Roots的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

3.2.3 再谈引用

  • Strong Reference 强引用
  • Soft Reference 软引用
  • Weak Reference 弱引用
  • Phantom Reference 虚引用

3.2.4 生存还是死亡

finalize()方法

3.2.5 回收方法区

“无用的类”:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法
    回收:
  • 提供 -Xnoclassgc 参数
  • 使用 -verbose:class以及XX:+TraceClassLoading、XX:+TraceClassUnLoading查看类加载和卸载信息

3.3 垃圾收集算法

3.3.1 标记-清除算法

Mark-Sweep
不足

  • 效率问题
  • 空间问题

3.3.2复制算法

Copying
新生代对象
一块较大的Eden空间和两块较小的Survivor空间
Handle Promotion 分配担保

3.3.3 标记-整理算法

Mark-Compact
老年代对象

3.3.4 分代收集算法

Generational Collection

3.4 HotSpot的算法实现

对算法得执行效率有严格的考量

3.4.1 枚举根节点

  • 逐个检查引用,消耗很多时间
  • GC停顿
  • 准确式GC:OopMap

3.4.2 安全点

  • OopMap使得GC的空间成本变高
  • Safepoint安全点
    • 是否具有让程序长时间执行的特征
    • 指令序列复用,例如方法调用、循环跳转、异常跳转
  • 多线程
    • Preemptive Suspension 抢先式中断
    • Voluntary Suspension 主动式中断

3.4.3 安全区域

Safe Region
在一段代码片段中,引用关系不会发生变化

3.5 垃圾收集器

内存回收的具体实现

3.5.1 Serial收集器

  • 最基本、发展历史最悠久的收集器
  • 单线程收集器

3.5.2 ParNew收集器

Serial收集器的多线程版本

  • 并行(Parallel):指多条垃圾收集器并行工作,但此时用户线程仍然处于等待状态
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行,用户程序在继续运行,而垃圾收集程序运行于另一个CPU上

3.5.3 Parallel Scavenge收集器

  • 达到一个可控制的吞吐量(Throughput)
    • 控制最大垃圾收集停顿时间-XX:MaxGcPauseMillis参数
    • 直接设置吞吐量大小的-XX:GCTimeRatio参数
  • “吞吐量优先”收集器
  • 开关参数-XX:+UseAdaptiveSizePolicy,GC自适应的调节策略(GC Ergonomics)

3.5.4 Serial Old收集器

  • Serial收集器的老年代版本
  • Server模式下:
    • 在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用
    • 作为CMS收集器的后备预案

3.5.5 Parallel Old收集器

  • 使用多线程和“标记-整理”算法
  • Parallel Scavenge加Parallel Old收集器

3.5.6 CMS收集器

  • Concurrent Mark Sweep
  • 一种以获取最短回收停顿时间为目标的收集器
  • 互联网站或者B/S系统的服务端上
  • 4个步骤:
    1. CMS initial mark 初始标记:GC Roots能关联到的对象
    2. CMS concurrent mark 并发标记:GC RootsTracing
    3. CMS remark 重新标记:修正
    4. CMS concurrent sweep 并发清除
  • Concurrent Low Pause Collector 并发低停顿收集器
  • Incremental Concurrent Mark Sweep/i-CMS 增量式并发收集器,不提倡
  • 缺点:
    • 无法处理浮动垃圾,Floating Garbage
    • 大量空间碎片

3.5.7 G1收集器

  • Garbage First,G1 收集器是当今收集器技术发展的最前沿成果之一
  • 面向服务端应用的垃圾收集器
  • 特点:
    • 并行与并发
    • 分代收集
    • 空间整合
    • 可预测的停顿
  • 步骤:
    • Initial Marking 初始标记
    • Concurrent Marking 并发标记
    • Final Marking 最终标记
    • Live Data Counting and Evacuation 筛选回收

3.5.8 理解GC日志

  • GC发生的时间,垃圾收集的停顿类型,GC发生的区域,GC前Java堆已使用容量Z->GC后Java堆已使用容量(Java堆总容量),该内存区域GC所占用的时间

3.5.9 垃圾收集器参数总结

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

内存分配与回收策略

内存分配规则

3.6.1 对象先在Eden分配

  • 虚拟机提供-XX:+PrintGCDetails,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况
  • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作
  • 老年代GC(Major GC/Full GC):指发生在老年代的GC

3.6.2 大对象直接进入老年代

  • 大对象:需要大量连续内存空间的Java对象,例如很长的字符串以及数组、
  • 虚拟机提供-XX:PretenureSizeThreshold参数,令大于这个设置值直接在老年代分配,只对Serial和ParNew两款收集器有效

3.6.3 长期存活的对象将进入老年代

  • 年龄计数器
  • 对象晋升老年代的年龄阈值,参数-XX:MaxTenuringThreshold

3.6.4 动态对象年龄判定

  • 如果在Survivor空间中相同年龄的所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代

3.6.5 空间分配担保

  • 只要老年代的连续空间大于新生代对象或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。
  • Handle Promotion Failure 担保失败

虚拟机性能监控与故障处理工具

4.1 概述

  • 知识、经验是关键基础,数据是依据,工具是运用知识处理数据的手段
  • 数据包括:运行日志、异常堆栈、GC日志、线程快照(threaddump/Javacore文件)、堆转储快照(heapdump/hprof文件)等

4.2 JDK命令行工具

  • 监视虚拟机和故障处理工具

4.2.1 jps:虚拟机进程状况工具

  • JVM Process Status Tool
  • 列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class, main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(Local Virtual Machine Identifier, LVMID)
  • 对于本地虚拟机进程来说,LVMID与操作系统的进程ID(Process Identifier, PID)

4.2.2 jstat: 虚拟机统计信息监视工具

  • JVM Statistics Monitoring Tool
  • 用于监视虚拟机各种运行状态信息的命令行工具,显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据

4.2.3 jinfo: Java配置信息工具

  • Configuration Info for Java
  • 实时地查看和调整虚拟机各项参数

4.2.4 jmap: Java内存映像工具

  • Memory Map for Java
  • 生成堆转储快照,查询finalize执行队列、Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。

4.2.5 jhat: 虚拟机堆转储快照分析工具

  • JVM Heap Analysis Tool
  • 与jmap搭配使用,来分析jmap生成的堆转储快照

4.2.6 jstack: Java堆栈跟踪工具

  • Stack Trace for Java
  • 用于生成虚拟机当前时刻的线程快照

4.2.7 JIT生成代码反汇编

  • HSDIS是一个Sun官方推荐的HotSpot虚拟机JIT编译代码的反汇编插件
  • 作用是让HotSpot的-XX:+printAssembly指令调用它来把动态生成的本地代码还原成为汇编代码输出

4.3 JDK的可视化工具

可视化工具:JConsole和VisualVM

4.3.1 JConsole: Java监视与管理控制台

  • Java Monitoring and Management Console
  • 基于JMX的可视化工具、管理工具。
1. 启动JConsole
  • JDK/bin/jconsole.exe
  • 概述、内存、线程、类、VM摘要、MBean
2. 内存监控
  • 可视化的jstat命令
  • 虚拟机参数为-Xms100m-Xmx100m-XX:+UseSerialGC,这段代码的作用是以64K/50毫秒的速度往Java堆中填充数据,一共填充1000次
3. 线程监控
  • 可视化的就stack命令
  • 线程长时间停顿的主要原因有:等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待(活锁和死锁)

4.3.2 多合一故障处理工具

VisualVM(All-in-One Java Troubleshooting Tool)是目前为止发布的功能最强大的运行监视和故障处理程序

1. VisualVM兼容范围与插件安装
  • 基于NetBeans平台开发
  • 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)
  • 监视应用程序的CPU、GC、堆、方法区以及线程的信息(jstat、jstack)
  • dump以及分析堆转储快照
  • 方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法
  • 离线程序快照:收集程序的运行时配置、线程的dump、内存dump等信息建立一个快照,可以将快照发送开发者进行Bug反馈
2. 生成、浏览堆转储快照
  • 在“应用程序”窗口中右键单击应用程序节点,然后选择“堆Dump”
  • 在“应用程序”窗口中双击应用程序节点以打开应用程序标签,然后再“监视”标签中单击“堆dump”
3. 分析程序性能

在Profiler页签中,VisualVM提供了程序运行期间方法级的CPU执行时间分析以及内存分析

  • CPU分析,统计每个方法的执行次数、执行耗时
  • 内存分析,统计每个方法关联的对象数以及这些对象所占的空间
4. BTrace动态日志跟踪
  • 在不停止目标程序运行的前提下,通过HotSpot虚拟机的HotSpot技术动态加入原本并不存在的调试代码
  • HotSwap技术,代码热替换技术

第五章 调优案例分析与实战

5.1 概述

故障处理和调优

5.2 案例分析

1. 高性能硬件上的程序部署策略

两种方式:

  • 通过64位JDK来使用大内存
    内存回收导致的长时间停顿
  • 使用若干个32位虚拟机建立逻辑集群来利用硬件资源
    尽量避免节点竞争全局的资源
    很难最高效率地利用某些资源池
    各个节点受到32位的内存限制
    大量使用本地缓存(如大量使用HashMap作为K/V缓存)的应用把本地缓存改为集中式缓存
2. 集群间同步导致的内存溢出

JBossCache是基于自家的JGroups进行集群间的数据通信,JGroups使用协议栈的方式来实现收发数据包的各种所需特性自由组合。

3. 堆外内存导致的内存溢出错误
  • Direct Memory: 可通过-XX:MaxDirectMemorySize调整大小,内存不足时抛出OutOfMemoryError或者OutOfMemoryError:Direct buffer memory
  • 线程堆栈
  • Socket缓存区
  • JNI代码
  • 虚拟机和GC

5.2.4 外部命令导致系统缓慢

  • 执行Shell脚本是通过Java的Runtime.getRuntime().exec()方法调用的,是非常消耗资源的操作
  • 首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用这个新的进程执行外部命令,最后再退出这个进程。

5.2.5 服务器JVM进程崩溃

通知OA门户方修复无法使用的集成接口,并将异步调用改为生产者/消费者模式的消息队列实现后,系统恢复正常

5.2.6 不恰当数据结构导致内存占用过大

空间效率:

  • 在HashMap<Long, Long>结构中,只有Key和Value所存放的两个长整型数据有效数据,共16B(2×8B)。
  • 这两个长整型数据包装成java.lang.Long之后,就分别具有8B的MarkWord、8B的Klass指针,再加8B存储数据的long值,共2×24B
  • 两个对象组成Map.Entry之后,又多了16B的对象头,然后一个8B的next字段和4B的int型的hash字段,为了对齐,还必须添加4B的空白填充,共32B
  • 最后还有HashMap中对这个Entry的8B的引用,共8B
  • 实际消耗内存为(Long(24B)×2)+Entry(32B)+HashMapRef(8B)=88B
  • 16B/88B=18%

5.2.7 由Windows虚拟内存导致的长时间停顿

  • 在Java的GUI程序中要避免这种现象,可以加入参数“Dsun.awt.keepWorkingSetOnMinimize=true”来解决

5.3 实战:Eclipse运行速度调优

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值