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


自动内存分配管理机制
  • Java虚拟机栈
    • 这里面存储的是Java方法的内存模型
    • 这里会有两种异常情况
      • StackOverFlowError
      • OutOfMemoryError
  • 本地方法栈
    • 存储的是Native方法服务
    • Native方法根据系统环境的不同而不同
    • 这里也会有这两种异常情况
      • StackOverFlowError
      • OutOfMemoryError
  • Java堆
    • 存放对象实例的空间,几乎所有的对象实例都在这里分配,所以也被称为GC堆。
    • 是java虚拟机管理的最大的一块空间,跟随虚拟机启动而创建。
    • 划分
      • 新生代
      • 老生代
      • 元空间
        • 它分配在物理内存,1.8以前叫“永久代”,永久代在堆内存中分配,功能基本相同
        • 这是为了兼容HotSpot和JRockit诞生的,这两个虚拟机都被Oracle收购,故要兼容
    • 细分
      • Eden空间
      • From Survior空间
      • To Survior空间
    • 对空间是可以扩展的,扩展参数
      • -Xmx:堆内存的最大空间
      • -Xms:堆内存的初始空间
      • 调优时最好将初始空间大小等于最大空间,取消伸缩区。
    • 可能抛出的异常
      • OutOfMemoryError
  • 方法区
    • 存储已被加载的类信息、常量、静态变量、即时编译器编译的代码等数据。
  • 运行时常量池
    • 用于存放编译器生成的各种字面量和符号引用
    • 具备动态性
    • 用的较多的是String#intern()方法
    • 可能抛出的异常
      • OutOfMemoryError

垃圾收集器与内存分配策略
  • 堆和方法区是回收的重点
    • 程序计数器、虚拟机栈、本地方法栈随线程而生和死,,内存的分配和回收有确定性,不必考虑过多的回收问题。
    • 一个接口的多个实现类需要的内存可能不一样,一个方法的多个分支需要的内存也可能不一样,只有运行期间才能知道,所以这是内存关注的重点。
  • 引用计数算法
    • JVM没有采用引用技术算法
    • 弊端:引用计数算法很难解决对象之间的循环引用问题
  • 可达性分析算法
    • 原理:通过一系列成为“GC Roots”的对象作为起点,从这些起点开始向下搜索,走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连时,则证明对象是不可用的。
    • 可以作为GC Roots的对象分类
      • 虚拟机栈中引用的对象
      • 方法区中类静态属性引用的对象
      • 方法区中常量引用的对象
      • 本地方法栈中JNI(即Naitive方法)引用的对象
  • 引用的分类
    • 强引用
    • 软引用
    • 弱引用
    • 虚引用
  • 对象的回收过程
  1. 找到没有与GC Roots引用链相连的对象
  2. 被GC进行第一次标记
  3. 检查该对象是否覆盖finalize()方法,或finalize()方法已被调用过,如果是,则对象不会被回收,对象获得一次免死机会
  4. 如果没有,则被扔进F-Queue队列中
  5. 如果finalize()还没有关联自己,对象基本可以被回收了
  6. 注意:对象的finalize()方法只会被系统自动调用一次
  • 回收方法区
    • 回收废弃常亮的条件
      • 没有对象引用这个常亮
      • 没有其他地方引用这个字面常亮
    • 类的是否可以回收判定,3者必须同时满足
      • 该类的所有实例都已经被回收(object)
      • 加载该类的ClassLoader已经被回收(classLoader)
      • java.lang.Class对象没有在任何地方被引用(clazz)
    • “可以”被回收不是“必然”被回收,一些参数配置
      • -Xnoclassgc
      • -verbose:class
      • -XX:+TraceClassLoading
      • -XX:TraceClassUnloading
  • 标记清除算法
    1. 标记出所有需要回收的对象
    2. 标记完成后,统一回收所有被标记的对象
  • 标记清除算法的缺点
    • 效率太慢
    • 会产生大量的内存碎片
  • 复制算法
    • 商业虚拟机采用的内存划分
      • 80%:eden可用空间
      • 10%:from survivor空间
      • 10%:to survivor空间
  • 标记整理算法
  • 分代收集算法
    • 商业虚拟机大都采用“分代收集算法”
    • 新生代采用复制算法
    • 老生代采用“标记-清理算法”或“标记-整理算法”
  • 据统计,1个对象的分配空间
    • 栈内存:4k
    • 堆内存:8k
  • 如果Eden的空间不足时,会触发一个Minor GC的垃圾回收
  • 堆内存的参数调整()
  • 伸缩区最好为空,即被其他空间占满,可以通过-Xms、-Xmx设置
  • 两个参数
    • -Xmx(最大内存)
    • -Xms(初始内存)
    • 真实使用空间space:-Xms <= space <= -Xmx
    • 最好配置:-Xmx == -Xms,这样避免了伸缩区的可调策略,从而提升了JVM的性能。
    • 默认内存是总内存的1/4
  • 在命令行中调整JVM内存参数
    • javac TestDemo.java
    • java -Xmx10m -Xms10m —XX:+PrintGCDetails TestDemo
  • 2个自带的监视工具
    • jVisualVM (bin目录下,可视化,但不常用)
    • jmap命令:jmap -heap PID (PID为“java”或“javaw”进程)
      • 查看进程Pid
        • windows命令:tasklist,(或在资源管理器中找,查看-选择列,选择pid)
        • linux命令:ps -ef
  • jvisualVM显示
  • jmap显示:
  • 年轻代
    • 所有的对象都在年轻代产生
    • 年轻代都用复制收集算法
    • 在Eden中的对象,经过了多次MinorGC(见上图)后,仍然存活下来了,那么这个对象将晋升到存活区。
    • 为什么有2个相同大小存活区?
      • 一块存活区为了晋升,一块存活区为了回收
      • 这两个存活区是可以互相换的,一个回收完毕之后又变成了晋升的目的。
      • 这两个空间中一定有一个是空的,用的就是复制算法。
      • 由于eden区会产生大量的临时对象,所以会频繁的发生Minor GC,所以在HotSpot中为了加快空间的内存分配,采用了两种技术
        • Bump-The-Pointer(适合单线程的操作)
        • TLAB(Thead -Local_Allocation Buffers)(更适合多线程的操作)
    • 设置年轻代
    • 所以,eden区要比存活区大
    • 大部分的情况下不需要设置存活区的配置
  • 老年代
    • 在老年代里会使用两种算法结合的方式实现GC的处理:整理 – 压缩
    • 整理
      • 注意:在这个对象空间回收的过程之后,所有在老年代被回收的对象,并没有进行被进行空间的整理。所以在老年代中最头疼的问题就是碎片的整理,所以老年代比较适合放那些长期被使用,并且不会轻易回收的大对象。
    • 压缩
      • 使用的是标记清除压缩的算法,间接实现了碎片的整理
    • 老年代调整参数
    • 范例:设置老年代的一些参数
      • java -Xmx10m -Xms10m -XX:PretenureSizeThreshold= 512k TestDemo
      • 表示超过512k的对象会直接保存到老年代,这就是说这个对象不会再触发MinorGC
  • 永久代(jdk1.8后消失了)
    • 特点:永久代的内存不会被回收。
    • 但是在jdk 1.8之后这些设置基本无效,所以这块空间可以不必过多关注。
  • 元空间(jdk1.8之后才有)
    • 本质功能跟永久代基本相同
    • 区别是:
      • 永久代使用的是jvm的堆内存
      • 而元空间使用的是物理内存
  • JVM垃圾收集策略
    • 有6中组合策略
  • 主要可以按照下面的方式进行配置:
  • 主要参数
  • MAT( Memory Analyzer Tool)使用方法
    • mat是分析dump快照的工具,使用前必须先导出dump文件,有两种方式可以导出dump快照
      • 使用参数在出现Error时由系统自动执行导出快照,两个必须有的参数为
        • -XX:+HeapDumpOnOutOfMemory -XX:HeapDumpPath=d:\proj.bin
        • 或使用-XX:+HeapDumpOnCtrlBreak参数,使用ctrl+break快捷键导出
        • 或在Linux通过kill -3 pid 吓唬jvm也能导出dump文件
      • 在执行时,使用命令:jmap -dump:live,format=b,file=d:\User.bin Pid 导出文件
    • 经测试,将导出的文件后缀改为bin格式,效果比较好,其他格式可能会出现解析失败的问题。
    • 然后就可以将这个导出的文件通过MAT打开了
  • 关于3中JVM模式
垃圾收集器
按位置划分
  • 新生代
    • Serial收集器(串行)
    • ParNew收集器
    • Paraller Scavenge收集器
  • 老年代
    • Paraller Old 收集器
    • Serial Old 收集器
    • CMS(Concurrent Mark Sweep)
  • G1
并行和并发
  • 并行(parallel):指多条垃圾收集线程并行工作,但此时用户线程仍处于等待的状态,必须等待垃圾收集完毕用户线程才能正常工作
  • 并发(concurrent):只用户线程与垃圾收集线程同时执行,用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
各GC具体介绍
  • Serial 收集器
    • 是最基本、最悠久的收集器,目前已被淘汰
    • 是单线程的收集器,只会使用一个CPU或一条收集线程去完成垃圾收集工作
    • 在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop The World, STW)。
    • 适合用在单机模式下
  • ParNew收集器
    • “是Serial 收集器的多线程版本”
    • 适合工作在Server模式下
  • Parallel Scavenge收集器
    • 功能与ParNew收集器相同
    • 着重点在于:它出现的目标是提高吞吐量(Throughtput)
    • 吞吐量:处理用户代码的时间 / 总时间,
    • 99%吞吐量的定义:总共运行100分钟,1分钟用来处理垃圾收集,则(100-1)/ 100 = 99%
    • 控制吞吐量的3个参数
      • -XX:MaxGCPauseMillis:最大垃圾收集的时间参数
      • -XX:GCTimeRatio:直接设置吞吐量大小
      • -XX:+UseAdaptiveSizePolicy:是一个开关,如果打开,就不需要手工指定新生代的大小、晋升老年代对象年龄等细节参数了,细腻机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数已提供最合适的停顿时间和最大吞吐量。(GC自适应的调节策略)
  • Serial Old收集器
    • 是Serial收集器的老年代版本
    • 仍然是一个单线程的收集器
    • 也是运行在单机模式下
  • Parallel Old收集器
    • 是Parallel Scavenge收集器的老年代版本
  • CMS
    • 这是第一款真正意义上的并发(Concurrent)收集器,它第一次实现了让垃圾收集线程与用户线程同时工作
    • 目标:以获取最短回收停顿时间为目标
    • 主要工作在B/S模式下,减少系统停顿时间,增加用户体验
    • 使用算法:标记清除算法(Mark Sweep)
    • 具体垃圾回收步骤
      1. 初识标记
      2. 并发标记
      3. 重新标记
      4. 并发清除
  • G1收集器(Garbage First)
    • 目标:替换掉CMS收集器
    • 4大特点
      • 并行与并发
      • 分代收集
      • 空间整合
      • 可预测的停顿

分享到:

发表评论

昵称

抢个沙发呗~~~