JVM学习笔记

java内存区域

  • java内存区域划分为:java堆、方法区(以及运行时常量池)、本地方法栈、虚拟机栈、程序计数器
  • 线程共享的区域是:java堆、方法区;线程隔离的是:本地方法栈、虚拟机栈、程序计数器。
  • 异常:程序计数器既不抛出StackOverFlow也不抛出OutOfMemoryError;java堆抛出OutOfMemory;本地方法栈和虚拟机栈两者都抛出;方法区抛出OutOfMemoryError
  • 本地方法栈和虚拟机栈生命周期与线程一致,每个方法运行时都会创建一个栈帧。
  • JDK 1.7中,常量池从方法区中移除,到了堆中;而JDK 1.8移除了方法区,取而代之的是元空间(MetaSpace) 元空间和永久代类似,区别在于元空间不在虚拟机中,而是在本地内存中,所以理论上是只受本地内存的约束;当然实际上还是可以通过参数来设定它的大小

HotSpot

  • 对象分配保证原子性:采用CAS+失败重试的方式
  • 访问对象的方式:采用直接指针访问,而非句柄访问。
  • HotSpot中不区分本地方法栈和虚拟机栈
  • HotSpot采用可达性算法确定对象是否应该被回收,而不是引用计数法,避免重复引用
  • 采用OOPMap(普通对象指针映射)来达到准确式GC

JVM参数

  • -Xms:设定堆的最小值
  • -Xmx:设定堆的最大值,如-Xmx=-Xmx,则堆不能自动扩展
  • -Xss:设定栈的大小
  • -Xoss:设定本地方法栈大小(存在但无效)
  • -XX:PermSize/-XX:MaxPermSize  设定方法区大小.
  • -XX:MaxDirectMemorySize:设定直接内存的大小(NIO常用)
  • -XX+UseConcMarkSweepGC:使用CMS收集器GC,同时将ParNew作为默认的新生代收集器
  • -XX:MaxGCPauseMillis:最大垃圾收集时间
  • -XX:GCTimeRRatio:设置吞吐量大小(运行用户代码时间/(运行用户代码时间+GC时间))
  • -XX:UseAdaptiveSizePolicy:设置自适应策略,通过设置-XX:MaxGCPauseMillis(更关注停顿时间)或-XX:GCTimeRRatio(更关注吞吐量),给虚拟机设定一个优化目标方向。
  • -XX:CMSInitiatingOccupancyFraction:CMS收集器GC触发的老年代占用率阈值
  • -XX:PretenureSizeThrreshold设定大对象定义的大小阈值
  • -XX:MaxTenuringThershold:“长寿对象”进入老年代的年龄阈值

内存溢出

  • 栈存在两种异常OOM和SOF,但在单线程情况下只会出现StackOverFlow(SOF),只有在多线程情况下才会出现OOM,且每个线程分配的栈空间越大,则可建立的线程数量越少,越容易出现OOM。

对象相关

  • HotSpot虚拟机创建对象过程:类加载检查->内存分配->内存空间初始化为0值->对对象进行必要的设置(对象头)
  • 对象可以分为三个部分:对象头、实例数据、对齐填充。对象头存储对象包括本身运行时的属性,比如哈希码,锁状态标志;另一部分是类型指针;实例数据时对象真正存储的信息;对齐填充则是,如果对象的大小不为8的倍数,则填充为8的倍数(HotSpot要求对象大小必须是8的整数倍)。
  • 对象的访问包括:句柄访问/直接指针两种,HotSpot使用直接指针。

异常

  • 堆:OOM,只要不断的创建对象并保证可达性(HotSpot采用可达性算法确定对象是否应该被回收,而不是引用计数法,避免重复引用)
  • 栈(虚拟机栈/本地方法栈):OOM&SOF,在单线程情况下只会出现SOF,即调用的方法深度超过了设定的栈大小(递归过深);在多线程情况下能出现OOM,栈空间越大,那么允许存在的线程数越少,则越有可能出现OOM
  • 方法区/运行时常量池:OOM。运行时动态创建大量类,常出现于各种框架搭配(SSM/SSH)使用字节码动态代理技术(CGlib以及JDK动态代理)大量创建新的类。
  • 本机直接内存溢出:OOM。NIO技术,特点是在Heap Dump中没有明显的异常,入如果Dump文件很小,又直接或间接的使用了NIO,不妨考虑一下

垃圾收集器和内存分配策略

  • 在任何情况下都不建议采用finalize()方法去关闭资源。
  • finalize()方法优先级很低,系统对于任何一个对象的finalize()方法都只会调用一次。
  • 回收方法区(永久代):可以不要求在方法区实现垃圾收集:主要收集废弃的常量和无用的类。比如前面提到的字符串常量"java"(sun.misc.Version)如果没有String对象引用它,那么它就是废弃的,在必要情况下,它会被回收。而无用类进行回收主要出现在大量使用反射,动态代理、JSP、OSGi。

垃圾收集算法

  • 标记-清除算法:通过可达性算法标记对象;通过筛选进入F-Queue(覆盖或实现了finalize()方法)再进行一次可达性分析标记。
  • 复制算法
  1. 优点:实现简单,运行高效,不产生内存碎片,相对规整
  2. 缺点:有较大的内存浪费,在存活对象较多的情况下需要进行较多的复制操作,会降低效率,因而适合对象“朝生暮死”的区域(新生代)
  3. 适用:现代商业虚拟机都采用本算法回收新生代,将新生代划分为一块较大的Eden和块较小 的Survivor区。HotSpot默认Eden:Survior=8:1(其中一块的大小)
  4. 操作:每次使用Eden和一块Survior区,当GC时,将两个区的存活对象一次性复制到另外一块Survior区上,最后清理掉Eden区和开始那一块Survior区的空间。
  5. 其他:如果Survior区空间不够用,则会用老年代进行分配担保(即老年代“借出”一部分空间给Survior区存放上一次GC存活下来的对象)
  • 标记-整理算法
  1. 优点:不需要内存其他区域分配担保,内存区域相对规整
  2. 缺点:在标记-清除算法的基础上多了一步整理操作,效率相对降低
  3. 适用:内存区域存在大量存活对象
  4. 操作:同标记-清除,在标记后将所有存活对象向一端移动,然后清除边界以外的所有内存,获得相对规整的内存,减少内存碎片
  • HotSpot实现
  • 枚举根节点
  • 可达性分析的GC Roots选取:全局性引用(如静态属性/常量);执行上下文(栈帧中的本地变量表)
  • GC时要求同步、一致性(即GC时对象引用关系等不能再发生改变),因此GC时JAVA的执行线程都必须被停顿。
  • 主流的虚拟机都采用的准确式GC:即虚拟机有办法知道内存中某个位置到底存的是数据还是引用的地址。
  • 安全点
  • 程序并非在所有的地方都能停顿下来开始GC,可以的点被称为安全点
  • 安全点记录了OOPMap中引用的地址
  • 最明显特征:指令序列复用,例如方法调用、循环跳转、异常跳转等
  • 两种方法使得所有线程(除JNI调用的)到安全点再停顿:主动式中断、抢先式中断(基本不用)
  • 主动式中断:设定一个标志,由线程自己去轮询这个标志,标志与安全点重合
  • 安全区域
  • 对于安全点的补充:1、安全点针对的是运行期的线程,如果线程在Sleep状态或Block状态,那么显然线程无法“跑”到安全点再进行停顿。
  • 一段代码片段中,引用保持一致性。在这个区域内的任意地方开始GC都是安全的,即安全区域内每个点都是安全点。
  • 首先表示自己(线程)已经进入了安全区域(Safe Region),这样在GC时,标识为SR的线程都不会管了;而在线程要离开SR时,需要检查是否完成了根节点枚举(或是已经完成了GC),如果完成了就可以离开,否则需要等到收到可以安全离开SR区域的信号为止。

垃圾收集器

  • JDK 1.7  一共有7种收集器
  • 新生代收集器有:Serial、ParNew、Parallel Scavenge、G1(新生代和老年代共有)
  • 老年代收集器有:CMS、Serial Old(MSC)、Parallel Old、G1(共有)
  • Serial收集器
  1. 特点:单线程、收集时暂停其他线程,对新生代采用复制算法,老年代使用标记-整理算法;可与CMS收集器配合工作
  2. 优点:专注收集,有最高的单线程效率。简单而高效
  3. 缺点:需要暂停其他所有的工作线程
  4. 适用:运行在Client模式下默认新生代收集器
  • ParNew收集器
  1. 特点:Serial收集器的多线程版本、可与CMS收集器配合工作
  2. 优点:多线程并行收集,在目前CPU多核情况下表现相对于Serial更好
  3. 缺点:在单线程效率上不如Serial,甚至在双CPU情况下也不一定能超过。
  4. 适用:运行在Server模式下虚拟机默认新生代收集器
  • Parallel Scanvenge收集器
  1. 特点:吞吐量优先、高效利用CPU、自适应GC策略:-XX:UseAdaptiveSizePolicy
  2. 优点:高效利用CPU,和ParNew类似,都是并行多线程收集器
  3. 缺点:和ParNew类似
  4. 适用:手动优化困难时,对CPU吞吐量需求高,新生代收集器
  • Serial Old收集器
  1. 特点:在JDK1.5之前与Parallel Scanvenge搭配使用;作为CMS收集器的后备预案在出现concurrent mode failure时使用
  2. 优点:同Serial
  3. 缺点:同Serial
  4. 适用:运行在Client模式老年代默认收集器
  • Parallel Old收集器
  1. 特点:Parallel Scanvenge的老年代版本,注重吞吐量;JDK 1.6才出现
  2. 优点:略
  3. 缺点:略
  4. 适用:Server端老年代
  • CMS收集器
  1. 特点:采用标记-清除算法、GC线程同用户线程并发执行;默认启动GC线程数=(CPU+3)/4;不能等待老年代接近满了再GC,需要提前
  2. 优点:并发、低停顿
  3. 缺点:1、对CPU资源非常敏感;2、CMS收集器无法处理浮动垃圾,可能出现concurrent mode failure从而导致FULL GC;3、因为是基于标记-清除算法的,所以容易产生大量碎片
  4. 适用:对低停顿要求很高的应用,如BS服务端
  5. 名词解释:浮动垃圾——因为CMS是并发的,所以会出现“边扫地边有人丢垃圾”的情况,这部分垃圾就叫做浮动垃圾;因为此时初始标记已经完成了(已经进入了并发标记),所以这部分垃圾只能等待下一次GC。
  6. 操作:1、初始标记(标记GC roots的直接后继)2、并发标记(进行GC Tracing,遍历进行可达性分析,时间最长的步骤,但可并发执行)3、重新标记(即修正并发标记期间对象引用发生变化的那部分记录)4、并发清除(GC)
  • G1收集器
  1. 特点:可预测的停顿时间、采用标记-整理算法、可用于新生代和老年代(因为它在堆里不分代,只是分成不同但大小相同的区域)、维护一个回收优先级列表(回收大小/回收时间);通过维护Remember Set来避免进行全堆扫描(其他收集器也是这样)
  2. 优点:充分利用多CPU,多核环境;可预测停顿时间
  3. 缺点:目前还不够稳定,JDK 1.7推出 用于取代CMS的地位
  4. 适用:与CMS类似,硬件性能强的服务端
  5. 名词解释:Remember Set——如果当前对象 (在Region(新生代/老年代)含有另外一个Region(代)的引用,那么会把它记录到这个集合里面,最后将它加入GC roots,避免进行全堆检索,降低Minor GC的效率
  6. 操作:类似于CMS。1、初始标记。2、并发标记。3、最终标记。4、筛选清除

内存分配与回收策略

  • 对象优先在Eden分配
  1. 对象优先在新生代Eden区分配,如果Eden区空间不够,则会发起一次Minor GC
  • 大对象直接进入老年代
  • 大对象典型的就是长数组或长字符串
  • 大对象对虚拟机的内存分配是一个坏消息,“朝生暮死”的大对象尤是。通过参数-XX:PretenureSizeThrreshold设定大对象定义的大小阈值
  • “长寿”对象将进入老年代
  • 都叫老年代了,里面对象如果年龄不算权重的话,有些名不副实。虚拟机给每个对象定义了一个“年龄”(每熬过一次GC年龄+1),如果年龄大于一定的界限值(默认是15),对象将进入老年代。这个阈值通过-XX:MaxTenuringThershold设置
  • 动态对象年龄判定
  • 有时候尽管年龄不够,但“资历”够了,也能进入老年代。要求就是Survior区中相同年龄的所有对象大小超过了整个Survior区的一半,那么这些对象就会进入老年代
  • 空间分配担保
  1. 在进行Minor GC之前会进行一次检查:判断老年代中的最大连续可用控件是否大于新生代所有对象的总空间(因为进行Minor GC最坏最极端的情况就是GC后所有对象仍然存活,而新生代采用的是复制算法,需要存活对象那么大的后备辅助空间用以存储这些存货对象)
  2. 如果不够,且HandlePromotionFailure被设置为不允许失败,那么会发生Full GC;或允许失败的情况下,但最大连续可用空间小于每次Minor GC后存活对象大小的平均值,也会发生Full GC。

调优案例分析

  • 高性能硬件上的部署策略
  1. 出现问题:每隔一段时间出现一次长时间GC
  2. 问题分析:控制full GC的关键是看大多数对象是否符合“朝生暮死 ”的新生代标准,不要出现过多大对象,尤其是成批量的大对象
  3. 目前主要有两种方式:64bit JDK来使用大内存;使用多个32bit虚拟机建立逻辑集群。
  • 64bit JDK
  1. 优点:可以充分利用高性能硬件,支持更高的内存分配。
  2. 缺点:目前而言,64位性能略低于32位。(2)需要保证程序足够稳定,否则由于堆实在太大,难以对堆快照进行分析。(3)由于指针膨胀等原因,64位普遍比32位更消耗内存。
  • 多个32位JDK
  1. 优点:64位相对的缺点
  2. 缺点:(1)由于有多个逻辑虚拟机,所以容易产生并发磁盘I/O异常。(2)32位限制物理内存上线为4G,堆最大只能到1.5G。(3)资源利用率不高,尤其是珍贵的连接池。(4)大量使用了本地缓存,这个可以通过集中式缓存改善。
  3. 操作:无Session复制的亲和式集群(即规定某个Id用户请求会话固定分配到某一个集群节点上),在前端搭载负载均衡。
  • 集群间同步导致的内存溢出
  • 堆外内存导致的溢出错误
  1. 直接内存溢出:通过-xx:MaxDirectMemorySize调整直接内存大小
  2. 线程堆栈(虚拟机栈/本地方法栈):SOF/OOM,通过-Xss调整栈大小。
  3. Socket缓存区:每个Socket连接都有Receive和Send两个缓存区,如果连接太多,会抛出IO异常,too many open files
  4. JNI代码:本地方法的内存也不在堆中
  5. 虚拟机和GC:.....    我要见师座!
  • 外部命令导致系统缓慢
  1. 比如shell、PHP等服务端脚本语言
  • 不恰当的数据结构
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值