空虚碧海

空虚碧海 查看完整档案

珠海编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

空虚碧海 发布了文章 · 10月18日

JVM系列【6】GC与调优6-常用参数汇总

JVM系列笔记目录

  • 虚拟机的基础概念
  • class文件结构
  • class文件加载过程
  • jvm内存模型
  • JVM常用指令
  • GC与调优

GC常用参数

  • -Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间
  • -XX:+UseTLAB 使用TLAB,默认打开
  • -XX:+PrintTLAB 打印TLAB的使用情况
  • -XX:TLABSize 设置TLAB大小
  • -XX:+DisableExplictGC 禁止代码中显示调用GC
  • -XX:+PrintGC 打印GC情况
  • -XX:+PrintGCDetails 打印GC详细过程
  • -XX:+PrintHeapAtGC 在打印GC过程中打印机堆情况
  • -XX:+PrintGCTimeStamps 打印GC时间戳
  • -XX:+PrintGCApplicationConcurrentTime (低) 打印应用程序时间
  • -XX:+PrintGCApplicationStoppedTime低) 打印暂停时长
  • -XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用
  • -verbose:class 类加载详细过程
  • -XX:+PrintVMOptions 打印虚拟机的参数
  • -XX:+PrintFlagsFinal 打印机最终的参数
  • -XX:+PrintFlagsInitial 打印机初始的参数
  • -Xloggc:opt/log/gc.log 指定GC存储的路径
  • -XX:MaxTenuringThreshold 指定升代年龄,最大值15
  • -XX:PreBlockSpin 锁自旋次数
  • -XX:CompileThreshold 热点代码检测参数

Parallel常用参数

  • -XX:SurvivorRatio survivor区比例
  • -XX:PreTenureSizeThreshold 指定大对象到底多大
  • -XX:MaxTenuringThreshold 指定升代年龄,最大值15
  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
  • -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

CMS常用参数

  • -XX:+UseConcMarkSweepGC 使用CMS垃圾回收器
  • -XX:ParallelCMSThreads CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小
  • -XX:+UseCMSCompactAtFullCollection 在FGC时进行压缩
  • -XX:CMSFullGCsBeforeCompaction 多少次FGC之后进行压缩
  • -XX:+CMSClassUnloadingEnabled CMS运行类卸载
  • -XX:CMSInitiatingPermOccupancyFraction CMS达到什么比例时进行Perm回收
  • GCTimeRatio 设置GC时间占用程序运行时间的百分比
  • -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

G1常用参数

  • -XX:+UseG1GC 使用G1
  • -XX:MaxGCPauseMillis 建议值,G1会尝试调整Young区的块数来达到这个值
  • -XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32M; 随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长
  • G1NewSizePercent 新生代最小比例,默认为5%
  • G1MaxNewSizePercent 新生代最大比例,默认为60%
  • GCTimeRatio GC时间建议比例,G1会根据这个值调整堆空间
  • ConcGCThreads 线程数量
  • InitiatingHeapOccupancyPercent 启动G1的堆空间占用比例
知识分享,转载请注明出处。学无先后,达者为先!
查看原文

赞 0 收藏 0 评论 0

空虚碧海 发布了文章 · 10月18日

JVM系列【6】GC与调优5-日志分析

JVM系列笔记目录

  • 虚拟机的基础概念
  • class文件结构
  • class文件加载过程
  • jvm内存模型
  • JVM常用指令
  • GC与调优

主要内容

分析PS、CMS、G1的回收日志,目标使大概能读懂GC日志。

测试程序

java的版本是1.8,测试思路是用死循环中不停分配1M大小的数组 ,这样在启动时候指定较小的您内存空间,很快就会产生GC。

import java.util.LinkedList;
import java.util.List;

public class HelloGC {
    public static void main(String[] args) {
        System.out.println("HelloGC!");
        List list = new LinkedList();
        for(;;) {
            // 死循环中不停分配1M大小的数组 
            byte[] b = new byte[1024*1024];
            list.add(b);
        }
    }
}

PS日志分析

  1. 启动命令:`java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+Us
    eParallelGC -XX:+PrintGCDetails HelloGC。指定JVM醉倒内存为60M,初始内存为40M,年轻代的大小是10M,-XX:+UseParallelGC指定垃圾回收器为PS+PO,-XX:+PrintGCDetails`打印出详细的GC回收日志。
  2. 日志

    D:\>java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseParallelGC   -XX:+PrintGCDetails  HelloGC
    -XX:InitialHeapSize=41943040 -XX:MaxHeapSize=62914560 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseCompresse
    dClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
    HelloGC!
    [GC (Allocation Failure) [PSYoungGen: 7292K->784K(9216K)] 7292K->6936K(39936K), 0.0035951 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    [GC (Allocation Failure) [PSYoungGen: 8112K->840K(9216K)] 14264K->14160K(39936K), 0.0034203 secs] [Times: user=0.03 sys=0.06, real=0.00 secs]
    
    [GC (Allocation Failure) [PSYoungGen: 8160K->736K(9216K)] 21481K->21224K(39936K), 0.0038512 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    
    [GC (Allocation Failure) [PSYoungGen: 8059K->832K(9216K)] 28547K->28488K(39936K), 0.0031367 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    
    [Full GC (Ergonomics) [PSYoungGen: 832K->0K(9216K)] [ParOldGen: 27656K->28287K(46080K)] 28488K->28287K(55296K), [Metaspace: 2710K->2710K(1056
    768K)], 0.0172805 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
    [GC (Allocation Failure) [PSYoungGen: 7325K->224K(8704K)] 35612K->35679K(54784K), 0.0034935 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    
    [GC (Allocation Failure) [PSYoungGen: 7542K->1248K(8704K)] 42997K->42847K(54784K), 0.0032856 secs] [Times: user=0.00 sys=0.00, real=0.00 secs
    ]
    [Full GC (Ergonomics) [PSYoungGen: 1248K->0K(8704K)] [ParOldGen: 41599K->42623K(51200K)] 42847K->42623K(59904K), [Metaspace: 2710K->2710K(105
    6768K)], 0.0056374 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
    [GC (Allocation Failure) [PSYoungGen: 6278K->1216K(8704K)] 48902K->48960K(59904K), 0.0034283 secs] [Times: user=0.00 sys=0.00, real=0.00 secs
    ]
    [Full GC (Ergonomics) [PSYoungGen: 1216K->0K(8704K)] [ParOldGen: 47744K->48768K(51200K)] 48960K->48768K(59904K), [Metaspace: 2710K->2710K(105
    6768K)], 0.0044365 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    [Full GC (Ergonomics) [PSYoungGen: 6272K->4096K(8704K)] [ParOldGen: 48768K->50816K(51200K)] 55040K->54912K(59904K), [Metaspace: 2710K->2710K(
    1056768K)], 0.0060174 secs] [Times: user=0.09 sys=0.02, real=0.01 secs]
    [Full GC (Ergonomics) [PSYoungGen: 6268K->6144K(8704K)] [ParOldGen: 50816K->50816K(51200K)] 57084K->56960K(59904K), [Metaspace: 2710K->2710K(
    1056768K)], 0.0043812 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    [Full GC (Allocation Failure) [PSYoungGen: 6144K->6144K(8704K)] [ParOldGen: 50816K->50803K(51200K)] 56960K->56948K(59904K), [Metaspace: 2710K
    ->2710K(1056768K)], 0.0164153 secs] [Times: user=0.09 sys=0.00, real=0.02 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
            at HelloGC.main(HelloGC.java:9)
    Heap
     PSYoungGen      total 8704K, used 6385K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
      eden space 7168K, 89% used 0x00000000ff600000,0x00000000ffc3c518,0x00000000ffd00000)
      from space 1536K, 0% used [0x00000000ffd00000,0x00000000ffd00000,0x00000000ffe80000)
      to   space 1536K, 0% used [0x00000000ffe80000,0x00000000ffe80000,0x0000000100000000)
     ParOldGen       total 51200K, used 50803K [0x00000000fc400000, 0x00000000ff600000, 0x00000000ff600000)
      object space 51200K, 99% used [0x00000000fc400000,0x00000000ff59cfd0,0x00000000ff600000)
     Metaspace       used 2739K, capacity 4486K, committed 4864K, reserved 1056768K
      class space    used 304K, capacity 386K, committed 512K, reserved 1048576K
    
    • YGC分析

file

  • FGC分析

file

  • 堆空间分析

file

CMS日志

回顾一下CMS回收主要4个阶段:初始标记、并发标记、最终标记、并发回收。

补充一个Card Table的概念

由于做YGC时,需要扫描整个OLD区,效率非常低,所以JVM设计了CardTable, 如果一个OLD区CardTable中某个Card有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card。在结构上,Card Table用BitMap来实现。
  1. 启动命令java -Xmn20M -Xmx20M -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC HelloGC。参数 -XX:+UseConcMarkSweepGC指定使用CMS垃圾回收器。
  2. 日志分析

    • YGC

      [GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]

      ParNew:年轻代收集器

      6144->640:收集前后的对比

      (6144):整个年轻代容量

      6585 -> 2770:整个堆的情况

      (19840):整个堆大小

    • CMS的回收

      // 初始标记
      //8511 (13696) : 老年代使用(最大)
      //9866 (19840) : 整个堆使用(最大)
      [GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
      
      //并发标记 这里的时间意义不大,因为是并发执行
      [CMS-concurrent-mark-start]
      [CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
      
      // 标记Card为Dirty,也称为Card Marking
      [CMS-concurrent-preclean-start]
      [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
      
      //最终标记 STW阶段
      //YG occupancy:年轻代占用及容量
      //[Rescan (parallel):STW下的存活对象标记
      //weak refs processing: 弱引用处理
      //class unloading: 卸载用不到的class
      //scrub symbol(string) table: 官方的解释cleaning up symbol and string tables which //hold class-level metadata and internalized string respectively
      //CMS-remark: 8511K(13696K): 阶段过后的老年代占用及容量
      //10108K(19840K): 阶段过后的堆占用及容量
      [GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
      
      // 最终标记 标记已经完成,进行并发清理
      [CMS-concurrent-sweep-start]
      [CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
      
      //重置内部结构,为下次GC做准备
      [CMS-concurrent-reset-start]
      [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
      

G1日志

回顾一下,G1是分region回收的,有YGC、MixedGC(类似CMS)、FGC。

官方提供了 Garbage First Garbage Collector Tuning, 可以参考。

  1. 启动命令java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC HelloGC,其中-XX:+UseG1GC指定使用G1。
  2. 日志分析

    //GC暂停
    // (G1 Humongous Allocation) (young) 大对象分配
    // initial-mark初始标记,这里是YGC混合老年代回收MixedGC
    [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0026947 secs]
    
    //这是一个GC线程 多线程回收的
       [Parallel Time: 2.1 ms, GC Workers: 8]
          [GC Worker Start (ms): Min: 137.2, Avg: 137.2, Max: 137.3, Diff: 0.1]
          [Ext Root Scanning (ms): Min: 0.0, Avg: 0.4, Max: 0.6, Diff: 0.6, Sum: 3.5]
          // 更新Rset
          [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
             [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
          [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
          [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.2, Diff: 0.2, Sum: 0.2]
          //拷贝存活对象
          [Object Copy (ms): Min: 1.1, Avg: 1.2, Max: 1.3, Diff: 0.2, Sum: 9.7]
          [Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.5]
             [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 8]
          [GC Worker Other (ms): Min: 0.1, Avg: 0.3, Max: 0.7, Diff: 0.6, Sum: 2.1]
          [GC Worker Total (ms): Min: 2.0, Avg: 2.0, Max: 2.0, Diff: 0.1, Sum: 16.0]
          [GC Worker End (ms): Min: 139.2, Avg: 139.3, Max: 139.3, Diff: 0.0]
       [Code Root Fixup: 0.0 ms]
       [Code Root Purge: 0.0 ms]
       [Clear CT: 0.1 ms]
       [Other: 0.5 ms]
          [Choose CSet: 0.0 ms]
          [Ref Proc: 0.1 ms]
          [Ref Enq: 0.0 ms]
          [Redirty Cards: 0.1 ms]
          [Humongous Register: 0.0 ms]
          [Humongous Reclaim: 0.0 ms]
          [Free CSet: 0.0 ms]
       [Eden: 2048.0K(12.0M)->0.0B(4096.0K) Survivors: 0.0B->1024.0K Heap: 5122.1K(20.0M)->4880.1K(20.0M)]
     [Times: user=0.00 sys=0.00, real=0.01 secs]
     // 以下是MixedGC的其它阶段 类似CMS
    [GC concurrent-root-region-scan-start]
    [GC concurrent-root-region-scan-end, 0.0008043 secs]
    [GC concurrent-mark-start]
    [GC concurrent-mark-end, 0.0001578 secs]
    [GC remark [Finalize Marking, 0.0001140 secs] [GC ref-proc, 0.0001270 secs] 
    
    // 卸载
    [Unloading, 0.0011719 secs], 0.0017205 secs]
     [Times: user=0.00 sys=0.00, real=0.00 secs]
     // 清理
    [GC cleanup 8034K->8034K(20M), 0.0004763 secs]
     [Times: user=0.00 sys=0.00, real=0.00 secs]
    
    //其它的GC线程类似
    ......
    
    //无法分配进行FGC
    [Full GC (Allocation Failure)  9855K->9842K(20M), 0.0029134 secs]
       [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 9855.3K(20.0M)->9843.0K(20.0M)], [Metaspace: 2710K->2710K(1056768K)]
     [Times: user=0.00 sys=0.00, real=0.00 secs]
    [GC concurrent-mark-abort]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
            at HelloGC.main(HelloGC.java:9)
            
    // 堆空间的情况
    Heap
     garbage-first heap   total 20480K, used 9842K [0x00000000fec00000, 0x00000000fed000a0, 0x0000000100000000)
      region size 1024K, 1 young (1024K), 0 survivors (0K)
     Metaspace       used 2739K, capacity 4486K, committed 4864K, reserved 1056768K
      class space    used 304K, capacity 386K, committed 512K, reserved 1048576K
    

总结

本篇对PS、CMS、G1的日志进行了较为详细的分析,基本可以参照着看懂垃圾回收的的过程。

知识分享,转载请注明出处。学无先后,达者为先!
查看原文

赞 0 收藏 0 评论 0

空虚碧海 发布了文章 · 10月17日

JVM系列【6】GC与调优4

JVM系列笔记目录

  • 虚拟机的基础概念
  • class文件结构
  • class文件加载过程
  • jvm内存模型
  • JVM常用指令
  • GC与调优

主要内容

上一篇 JVM系列【6】GC与调优3 记录JVM调优的概念和分析案例。这篇主要讲G1垃圾回收器、三色标记算法。

G1垃圾回收器

  1. 概念

G1(Garbage First)是服务端使用的垃圾回收器,用于多核、大内存的机器上,在大多数情况下可以实现指定的GC停顿时间,同时还保持较高的吞吐量。不同于之前的分代垃圾回收器,G1是逻辑上分代,物理上不分代。内存分为一个一个Region,有Eden区、Survivor区、Old区、还有Humongous区。

file

特点:并发回收;压缩空闲时间不会延长GC的暂停时间;更易预测的GC暂停时间;适用不需要很高的吞吐量的场景。

优点:追求吐吞量;追求响应时间:可以对STW进行控制;灵活: 分Region回收,优先回收花费时间少、垃圾比例高的Region。

  1. CSet和RSet

    CSet(Collection Set): G1一组可被回收的分区的集合;在CSet中存活的数据会在GC过程中被移动到另一个可用分区;CSet中的分区可以来自Eden空间、survivor空间或者老年代;CSet中的分区可以来自Eden空间、survivor空间或者老年代。

    RSet(RememberedSet):记录了其它Region中的对象到本region的引用;价值在于使得垃圾回收器不需要扫描整个堆就能找到谁引用了当前分区中的对象,只需要扫描RSet即可

file

了解:RSet与赋值的效率,由于RSet的存在,那么每次给对象赋值引用的时候,就得做一些额外的操作,指的是在RSet中做一下额外的记录(在GC中称为写屏障),这个写屏障不等于内存屏障。

  1. 新老年代的比例

    G1中新老年代的比例为5%-60%,一般不用手工指定,也不要手动指定,因为这是G1预测停顿时间的基准,G1去控制和预测STW就是通过动态调整这个比例来实现的。

  2. Region有多大?

    G1中Region不指定的时候,会根据实际的Heap大小来设定,取值范围为1 2 4 8 16 32M,设置参数为-XX:G1HeapRegionSize

  3. G1中的垃圾回收

    分YGC、FGC、Mixed GC类型。

    YGC: Eden空间不足触发,多线程并发执行。

    FGC:Old空间不足触发

    Mixed GC: 相当于CMS的回收过程,初始标记、并发标记、最终标记、筛选回收。触发Mixed GC的参数是-XX:InitiatingHeapOccupacyPercent, 默认为45%,当O区超过这个值启动MixedGC。

  4. G1是并行回收是否存在FGC?

    当分配对象非常快,回收不过来的情况下会产生FGC,jdk10以前是串行FullGC,之后是并行的FGC;

    如何避免FGC?

    降低MixedGC的触发一直,让MixedGC提早发生(默认是45%);扩内存;提高CPU性能(回收得快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)。

三色标记法

  1. 三色的含义:

    白色指的是未被标记的对象,灰色指自身被标记,成员变量未被标记的对象,黑色指自身和成员变量都已标记完成的对象。

file

  1. 漏标

    CMS和G1在垃圾标记的过程中存在一个难点:在标记对象的过程中,对象的引用关系正在发生变化,会产生漏标的现象。漏标产生的充分必要条件:在remark过程中,黑色指向了白色,如果不对黑色重新扫描,则会漏标,会把白色D对象当做没有新引用指向从而回收掉;并发标记过程中,删除了所有从灰色到白色的引用,则会产生漏标,此时白色对象应该被回收,D漏标。

file

  1. 如何解决漏标

    CMS用的是Incremental Update算法, 增量更新,关注引用的增加,A->D,把黑色重新标记为灰色,下次重新扫描A的属性。

    G1用的是SATB(snapshot at the beginning),关注引用的删除,当B->D引用消失时,要把这个“引用”推到GC的堆栈,保证D还能被GC扫描到。

    为什么G1使用STAB? 当B->D引用消失时,会把这个引用推到GC的堆栈,下次扫描时候会拿到这个引用,由于有RSet的存在,不需要扫描整个堆去查找白色的引用,效率比较高。

  2. 总结

    CMS使用的并发标记算法是三色标记+Incremental Update

    CMS使用的并发标记算法是三色标记+SATB

知识分享,转载请注明出处。学无先后,达者为先!
查看原文

赞 0 收藏 0 评论 0

空虚碧海 发布了文章 · 10月17日

JVM系列【6】GC与调优3

JVM系列笔记目录

  • 虚拟机的基础概念
  • class文件结构
  • class文件加载过程
  • jvm内存模型
  • JVM常用指令
  • GC与调优

调优前的基础概念

  1. 吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
  2. 响应时间:STW越短,响应时间越好
  3. 所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量。如科学计算、数据挖掘:吞吐量优先;网站、GU、 API 等追求响应时间。

什么是调优

  1. 根据需求进行JVM规划和预调优
  2. 优化JVM运行环境(慢、卡顿)
  3. 解决JVM运行过程中出现的各种问题(如OOM)
  • 根据需求进行JVM规划和预调优

    调优一般来说,从业务场景开始,没有业务场景的调优都是耍流氓;无监控 (压力测试,能看到结果),不调优。

    步骤

    1. ​ 熟悉业务场景
    2. 选择回收器组合,没有最好的GC,只有最适合的GC。追求响应时间或停顿时间选择CMS、G1、ZGC追求吞吐量 可选择PS。
    3. 计算内存需求(经验值 1.5G -16G)
    4. 选定CPU(越高越好)
    5. 设定年代大小,升级年代
    6. 设定日志参数,日志文件全放一个?10T日志怎么查。可以设置滚动日志如 -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause, 或是每天产生一个日志文件。
    7. 观察日志情况

    提供一个简单的案例分析

    案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?

    这个问题比较业余,因为很多不同的服务器配置都能支撑(1.5G -16G)。可以具体分析:一天中特定的高峰期1小时有360000订单集, 1000个订单/秒,可以根据经验值估算需要的内存配置。如果非要要计算,可以这么算:一个订单产生需要多少内存?512K * 1000,需要500M内存。

    从另外专业的角度考虑:要求响应时间100ms,这种情况下可以通过压测的方式找到符合的服务器配置。

  • 优化JVM运行环境(慢、卡顿)
    1. 提供一个简单的案例分析。

      有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G的堆.用户反馈网站比较缓慢,因此公司决定升级。新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了。

      1. 为什么原网站慢?很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢。
      2. 为什么会更卡顿?内存越大,FGC时间越长
      3. 如何解决?可以换垃圾回收器,如从PS 换为PN + CMS 或者是1.8以上的直接用G1。
    2. 系统CPU经常100%,如何调优?(面试高频)

      CPU100%那么一定有线程在占用系统资源,

      1. 找出哪个进程cpu高(top)
      2. 该进程中的哪个线程cpu高(top -Hp)
      3. 导出该线程的堆栈 (jstack)
      4. 查找哪个方法(栈帧)消耗时间长 (jstack)
      5. 对比工作线程占比高还是垃圾回收线程占比高
    3. 系统内存飙高,如何调优(面试高频)

      思路: 导出堆内存(jmap); 使用工具如(jhat jvisualvm mat jprofiler)分析
  • 解决JVM运行过程中出现的各种问题

    提供一个测试案例来说明分析过程和常用的工具,代码如下:

    /**

*/

public class T15_FullGC_Problem01 {

  private static class CardInfo {
      BigDecimal price = new BigDecimal(0.0);
      String name = "张三";
      int age = 5;
      Date birthdate = new Date();

      public void m() {}
  }

  private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
          new ThreadPoolExecutor.DiscardOldestPolicy());

  public static void main(String[] args) throws Exception {
      executor.setMaximumPoolSize(50);

      for (;;){
          modelFit();
          Thread.sleep(100);
      }
  }

  private static void modelFit(){
      List<CardInfo> taskList = getAllCardInfo();
      taskList.forEach(info -> {
          // do something
          executor.scheduleWithFixedDelay(() -> {
              //do sth with info
              info.m();

          }, 2, 3, TimeUnit.SECONDS);
      });
  }

  private static List<CardInfo> getAllCardInfo(){
      List<CardInfo> taskList = new ArrayList<>();

      for (int i = 0; i < 100; i++) {
          CardInfo ci = new CardInfo();
          taskList.add(ci);
      }

      return taskList;
  }

}


1. 启动命令`java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError T15_FullGC_Problem01`

2. 一般是运维团队首先受到报警信息(CPU、Memory)

3. top命令观察到问题:内存不断增长,CPU占用率居高不下

4. top -Hp 观察进程中的线程,哪个线程CPU和内存占比高

5. jps定位具体java进程;jstack  pid  定位线程状况,重点关注:WAITING BLOCKED

   >  waiting on <0x0000000088ca3310> (a java.lang.Object)
   >    假如有一个进程中100个线程,很多线程都在waiting on <xx> ,一定要找到是哪个线程持有这把锁。   怎么找?搜索jstack dump的信息,找<xx> ,看哪个线程持有这把锁处于RUNNABLE状态。

6. jinfo pid 查看JVM的情况,一般用处不大。

7. 通过jstat -gc 动态观察gc情况;或是阅读GC日志发现频繁GC;或是通过arthas观察gc情况。

8. 在线定位,查找有多少对象产生,注意大量的对象,使用`jmap -histo pid |head -20`

9. `jmap -dump:format=b,file=xxx pid `可以在线堆转储,但是要慎重影响很大。

   > 线上系统内存特别大,jmap执行期间对进程产生很大影响,甚至卡顿(电商不适合)。
   >
   > 如何解决? 启动时候设定参数HeapDumpOnOutOfMemoryError,OOM的 时候会自动产生堆转储文件;如果线上很多服务器备份(高可用),停掉这台服务器对其它服务没影响也可以在线堆转储;在线定位也可以用阿里的arthas。

10. 使用MAT/jhat/jvisualvm 进行dump文件分析。

    > 建议使用MAT/jvisualvm 装入分析,界面友好且支持复杂查询

11. 最难的一点:定位到代码中的问题

    > 示例代码的问题是:线程池使用不当引起OOM;不停new CardInfo对象不停地起定时线程去处理,导致这些对象越来越多

查看原文

赞 0 收藏 0 评论 0

空虚碧海 发布了文章 · 10月17日

JVM系列【6】GC与调优2.md

JVM系列笔记目录

  • 虚拟机的基础概念
  • class文件结构
  • class文件加载过程
  • jvm内存模型
  • JVM常用指令
  • GC与调优

了解HotSpot常用命令行参数

JVM的命令行参数参考: https://docs.oracle.com/javas...

-标准 所有版本支持
-X 非标准 特定版本支持
-XX 不稳定 下个版本可能会取消

常见和本文中可能用到的参数记录一下,具体垃圾回收器的参数后续调优的详细说明。

参数说明
-Xmx最大可用内存
-Xms初始内存,一般和-Xmx相同,避免重新分配
-Xmn年轻代大小,JVM内存=年轻代+老年代大小+永久代(一般64M)
-XX:+PrintFlagsInitial打印默认参数值
-XX:+PrintFlagsFinal打印最终参数值
-XX:+PrintGC <br/>-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
打印GC情况、GC详细日志、GC日志时间戳

常见垃圾回收器组合参数设定:(1.8)

如何查看JVM默认的垃圾回收器?

  1. java -XX:+PrintCommandLineFlags -version

file

  1. 通过GC日志来辨别是何种垃圾回收器

常见垃圾回收器组合参数设定:(1.8版本的)

>1. ​    -XX:+UseSerialGC = Serial New (DefNew) + Serial Old        小型程序使用,默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
>2. ​    -XX:+UseParNewGC = ParNew + SerialOld  这个组合已经很少用(在某些版本中已经废弃)
>3. ​    -XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old
>4. ​    -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
>5. ​    -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
>6. ​    -XX:+UseG1GC = G1


PS的GC日志详解

每一种垃圾回收器的日志是不一样。这里提供一个样例来详细解释PS的GC日志。

public class HelloGC{
    public static void main(String[] args){
        System.out.println("HelloGC!");
        List list = new ArrayList();
        for(;;){
            // 死循环中每次分配1M大小的数组,存放在list中,JVM内存不足的时候会产生GC
            byte[] b = new byte[1024*1024];
            list.add(b);
        }   
    }
}

编译后启动命令:java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+UseParallelGC HelloGC

这里设置了最大内存为60M,初始内存为40M,新生代的内存为10M,使用PS垃圾回收器,并打印GC的回收情况。程序运行很快就会产生GC日志。

回收的日志情况:

file

HeapDump情况,0x000xxxx内存地址指的是:起始地址、使用空间结束地址、整体空间结束地址;
file

知识分享,转载请注明出处。学无先后,达者为先!
查看原文

赞 0 收藏 0 评论 0

空虚碧海 发布了文章 · 10月15日

JVM系列【6】GC与调优1

JVM系列笔记目录

  • 虚拟机的基础概念
  • class文件结构
  • class文件加载过程
  • jvm内存模型
  • JVM常用指令
  • GC与调优

GC基础知识

  • 什么是垃圾

​ 没有任何引用指向的一个对象或多个对象(循环引用)

file

  • 如何找到垃圾

    • 引用计数(ReferenceCount),缺点:无法解决循环引用
    • 根可达算法(RootSearching),从根开始查找,找到对象是有用的,找不到的对象为垃圾。

file

什么是根? 注意结合上一篇博客 JVM系列【5】JVM常用指令-运行时数据区进行理解。

  1. 线程栈变量 JVM Stack、native method stack
  2. 静态变量 static refercences in method area、Clazz
  3. 常量池 runtime cosntant pool
  4. JNI指针 c/c++ 指针
  • 常见的垃圾回收算法

    1. 标记清除(mark sweep)

      缺点:位置不连续,产生碎片,效率偏低(需要进行两遍扫描)

      适用情况:算法相对简单,适用于存活对象比较多的情况
      file

    2. 拷贝算法 (copying)

      说明:没有碎片,浪费空间,复制移动对象,需要调整对象引用

      适用情况:适用于存活对象较少的情况 只扫描一次

      file

    3. 标记压缩(mark compact)

      说明:没有碎片,效率偏低(两遍扫描,指针需要调整)

      file

  • JVM堆内存分代模型(用于分代垃圾回收算法)

    堆内存分代模型是部分垃圾回收器使用的模型,除Epsilon ZGC Shenandoah 之外的GC都使用逻辑分代模型,但是G1 是逻辑分代 物理不分代,除此之外都是逻辑分代 而且物理分代。

    file

    分代模型中,分新生代和老年代,比例是1:2

    1. 新生代 = Eden区+ 2个survivor区

      YGC回收后,大多数的对象会被回收,活着的进入s0;

      再次YGC,活着的对象eden+s0 进入s1;

      再次YGC,活着的对象eden+s1 进入s0;

      年龄足够进入老年代,一般垃圾回收器是15,CMS是6;为什么一般垃圾回收器分代年龄是15?参考下 JVM系列【4】内存模型-对象头的内容有哪些,对象头中4位标识GC年龄,所以最大的年龄是2^4 -1 = 15

      s 区装不下,直接进入老年代。

    2. 老年代: 存放一些"顽固对象",老年代满了或是分配不下了会触发FCG。
    3. 永久代(Permanent Generation,JDK1.7以前)/元数据区(MetaSpace,JDK1.8以后):存放的是Class文件结构,永久代必须指定大小限制 ,元数据可以设置也可以不设置,无上限(受限于物理内存),字符串常量在1.7以前存放在永久代,1.8以后存放在堆中。
  • 垃圾回收类型和对象分配过程

    1. 垃圾回收类型

      上一个点解释了堆内存分代模型,这里统一下概念:MinorGC=YGC指的是新生代即Y区的垃圾回收,MajorGC=FGC指的是老年代分配不下或是满了发生的GC,包括新生代和老年代。

    2. 对象如何分配

      在之前的博客中 JVM系列【4】内存模型-new Object()面试6连问-对象如何分配,简单提过这一点。

file

​ 对照流程图,分配过程:

      > 1. new对象后优先在栈上分配,栈上分配的对象`pop`后就消失;
      > 2. 是否是大对象(Y区分配不下的对象)?是就分配到O区,O区发生FGC后回收
      > 3. 不是大对象,优先分配TLAB(Thread Local Allocation Buffer线程本地分配缓存)上,分配不下就分配到Eden区 。
    1. 了解栈上分配、TLAB、分配担保机制和升代

      1. 哪些对象栈上分配?

        线程私有对象 、无逃逸对象即离开代码块就没有引用的对象、支持标量替换的对象,如类中变量可以用基本变量替换。

      2. 线程本地分配Thread Local Allocation Buffer
        独占eden空间,默认1%;多线程时候不用竞争eden就可以申请空间,提高效率;可分配小对象
      3. 分配担保机制

        在新生代无法分配内存的时候,把新生代的对象转移到老生代,然后把新对象放入腾空的新生代。参考

      4. 对象何时进入老年代?

        超过MaxTenuringThreshold指定的次数;

        根据动态年龄计算进入老年代,Survivor空间中年龄从小到大的对象进行累加,当加入某个年龄段后,累加和超过survivor区域*TargetSurvivorRatio(默认50%)的时候,就从这个年龄段往上的年龄的对象进行晋升到老年代。参考

    • 常见的垃圾回收器

      常见的垃圾回收器有Serial/SerialOld、ParallelScavenge/ParallelOld、ParNew/CMS、G1、ZGC、Shenandoah、Epsilon。Serial/SerialOld、ParallelScavenge/ParallelOld、ParNew/CMS是分代的垃圾回收器,G1是逻辑上分代物理上不分代,ZGC和Shenandoah是不分代,Epsilon是JDK调试用的垃圾回收器。

      历史:JDK诞生,Serial追随,为了提供效率诞生PS,为配合CMS,诞生PN,CMS是1.4后期引入的,CMS是里程碑式的GC,但是CMS毛病比较多,因此目前没有任何一个JDK版本默认CMS。

      file

      • Serial

        年轻代 串行回收

        file

      • SerialOld

        老年代 串行回收

        file

      • PS(ParallelScavenge)

        年轻代 并行回收

        file

      • PO(ParallelOld)

        老年代 并行回收

        file

      • PN(ParNew)

        年轻代 增强版PS配合CMS的并行回收。PN 和 PS区别?PN 响应时间优先;PS吞吐量优先

      • CMS(ConcurrentMarkSweep)

        老年代并发的,垃圾回收和应用程序同时运行,降低STW的时间(200ms);

        CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定;

        CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收,STW无法忍受;

        • 如何解决碎片化:设定-XX:+UseCMSCompactAtFullCollection 默认开启;设定-XX:CMSFullGCsBeforeCompaction,默认为0 指经过多少次FGC才进行压缩。
        • 浮动垃圾问题解决?降低触发CMS的阈值,保持老年代有足够的空间;参数-XX:CMSInitiatingOccupancyFraction 指定使用CMS时老年代使用了指定阈值的内存后触发FGC,建议68-92%

        使用的算法:三色标记+Increamental Update

      • G1

        STW可以达到10ms

        算法:三色标记+SATB

      • ZGC

        STW号称可以达到1ms

        算法:颜色指针ColoredPointers + LoadBarrier

      • Shenandaoh

        算法:ColoredPointers + WriteBarrier

    • 垃圾回收器和内存大小的关系

      Serial 几十兆

      PS 上百兆-几个G

      CMS 20G左右

      G1 上百G

      ZGC 4T-16T(JDK13可以支持)

    知识分享,转载请注明出处。学无先后,达者为先!
    查看原文

    赞 0 收藏 0 评论 0

    空虚碧海 发布了文章 · 10月15日

    JVM系列【2】Class文件结构

    JVM系列笔记目录

    • 虚拟机的基础概念
    • class文件结构
    • class文件加载过程
    • jvm内存模型
    • JVM常用指令
    • GC与调优

    如何查看class字节码文件

    在idea中可以通过插件BinEd来查看二进制或十六进制文件。展示一个简单类的class文件源字节码,看不懂?请往下看。

    file

    更直观一点的可以在idea中安装插件jclasslib,选中类文件->View-> Show Bytecode With jclasslib。该插件清楚地看到.class文件的各个组成部分。

    file

    另外一种方式,通过javap查看。

    file

    Class文件结构

    文件结构由16个部分组成,约定下u4指占用的字节数为4,u2同理。这里指简单说明各个部分的含义,不做详细展开,如想深入了解,参考 JVM规范第4章节The class File Format

    • Magic Number class文件标志符 u4,ca fe ba be
    • Minor Version class文件小版本号 u2
    • Major Version 主版本号 u2,主版本+小版本号构成jdk版本,jdk8 默认 52.0,jdk7默认为51.0
    • constant_pool_count 常量池的大小u2
    • constant_pool 长度为constant_pool_count-1的数组,class文件中最复杂的部分
    • access_flag 类修饰符
    • this_class 类全名,指向常量池某个元素
    • super_class 父类名 指向常量池的某个元素
    • interface_count 实现的接口数量
    • interfaces 接口
    • fields_count 字段数量
    • fields 字段
    • method_count 方法数量
    • methods 方法
    • attribute_count 附加属性数量
    • attributes 附加属性

    总结

    class 文件结构很复杂,本为只是作简单介绍,为 JVM系列【3】Class文件加载过程JVM系列【4】内存模型作为基础知识点的铺垫。真正想深入理解class文件结构的,请阅读官方JVM规范。

    知识分享,转载请注明出处。学无先后,达者为先!
    查看原文

    赞 0 收藏 0 评论 0

    空虚碧海 发布了文章 · 10月12日

    JVM系列【5】JVM常用指令

    JVM系列笔记目录

    • 虚拟机的基础概念
    • class文件结构
    • class文件加载过程
    • jvm内存模型
    • JVM常用指令
    • GC与调优

    指令集分类

    基于寄存器的指令集

    基于栈的指令集

    Hotspot中的Local Variable Table相当于JVM的寄存器

    运行时数据区

    运行时数据区分6个部分: PC、JVM Stack、Heap、MethodArea、Native Method Stack、Direct Memory。

    file

    • PC: 程序计数器,存放下一条指令的位置。
    • JVM Stack:线程独有的栈,存放一个个栈帧(Stack Frame,每个方法对应一个栈帧。

      栈帧组成:

      • Local Variable Table 方法本地的参数表
      • Operand Stack 操作数栈
      • Dynamic Linking 动态链接,简单理解为指向Runtime Constant Pool中的符号链接;如果没有解释,将符号引用解析为直接引用的过程;如果已解释,直接用。感兴趣可以阅读下 Oracle官方JVM规范
      • Return address,返回地址,a() -> b(),方法a调用了方法b, b方法的返回值放在什么地方
    • Heap:所有线程共享的堆空间,具体在GC的章节探讨。
    • MethodArea:方法区,线程共享

      • 存放每一个class的结构
      • 方法区是逻辑上的概念,1.8前后有不同实现

        1.8版本前指的是永久代,字符串常量位于PermSpace,FGC不会清理,大小启动的时候指定,不能变。

        1.8后指的是元数据区(Meta Space),字符串常量位于堆,会触发FGC清理,不设定的话,就是最大的物理内存

      • 方法区中包含RunTime Constant Pool,是class中的常量池中的数据运行时存放的地方
    • Native Method Stack:调用了C/C++本地方法的栈。
    • Direct Memory:JVM可以直接访问的内核空间的内存(OS管理的内存),使用NIO,提高效率,实现零拷贝。

    运行时数据中PC、JVM Stack、Native Method Stack是线程独有的,而Heap、Method Area则是线程共享的。

    file

    • JVM常用指令

      如何查看指令的含义?idea中通过插件jclasslib直接点击指令查看,或是查阅JVM规范。

      • store 存入LVT
      • load 从LVT取
      • pop弹栈
      • add 加法
      • mul乘法
      • sub减法
      • invoke

        ​ InvokeStatic调用静态方法

        ​ InvokeVirtual调用可以多态的方法

        ​ InvokeInterface调用接口方法

        ​ InvokeSpecial调用可以直接定位,不需要多态的方法如private 方法、构造方法

        ​ InvokeDynamic调用lambda表达式或者反射或者其他动态语言scala kotlin,或者CGlib ASM,动态产生的类会用到的指令,JVM最难的指令

    知识分享,转载请注明出处。学无先后,达者为先!
    查看原文

    赞 0 收藏 0 评论 0

    空虚碧海 发布了文章 · 10月11日

    JVM系列【4】内存模型

    JVM系列笔记目录

    • 虚拟机的基础概念
    • class文件结构
    • class文件加载过程
    • jvm内存模型
    • JVM常用指令
    • GC与调优

    硬件层数据一致性

    - 存储器层次结构

    file

    从L6-L0 空间由大变小,速度由慢到快。

    -缓存一致性算法

    CPU实现缓存一致性的协议很多,其中intel 使用的MESI(Modified Exclusive Shared Or Invalid)协议。具体可以参考:[MESI--CPU缓存一致性协议](https://www.cnblogs.com/z0037...

    file

    现代CPU的数据一致性实现=缓存锁(MESI...) +总线锁

    -缓存行

    缓存读取时的单位,一般是64Byte
    使用缓存行的对齐能够提高效率

    -伪共享

    位于同一缓存行的2个不同的数据,被2个不同的CPU锁定,产生互相影响的伪共享问题。

    如何解决? 使用缓存行的对齐能够提高效率

    CPU乱序问题

    - 概念

    CPU为了提高执行效率,会在一条指令执行的过程中(比如去内存取数据(慢100倍)),去同时执行另一条指令,前提是两条指令没有依赖关系。具体参考:[现代cpu的合并写技术对程序的影响](https://www.cnblogs.com/liush...

    -合并写

    CPU上有一个WriteCombinBuffer,仅4个字节,比L1等级还高,某些写操作会合并在一起提交。[现代cpu的合并写技术对程序的影响](https://www.cnblogs.com/liush...

    -乱序证明

    CPU乱序现象有大佬写程序模拟出来了,具体参考: Memory Reordering Caught in the Act

    如何保证在特定情况下保证不乱序

    硬件级别

    X86 CPU级别内存屏障

    sfence
        store fence 在sfence指令前的写操作必须在sfence指令后的写操作前完成
        
    lfence
        load fence 在lfence指令前的读操作必须在lfence指令后的读操作前完成
        
    mfence
        mixed fence 在mfence指令前的读写操作必须在mfence指令后的读写操作前完成
    

    CPU原子指令

    如x86上的”lock …” 指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU
    

    总结: Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序

    JVM级别

    JSR113规范规定了4种内存屏障

    LoadLoad屏障
        对于语句Load1;LoadLoad;Load2,在Load2及后续读取指令要读取的数据被访问前,保证Load1要读取的数据被读取完毕
        
    StoreStore屏障
        对于语句Store1;StoreStore;Store2,在Store2及后续写操作执行前,保证Store1的写入操作对其它处理器可见
        
    LoadStore屏障
        对于语句Load1;StoreStore;Store2,在Store2及后续写操作被刷出前,保证Load1要读取的数据被读取完毕
        
    StoreLoad屏障
        对于语句Store1;StoreStore;Load2,在Load2及后续读取指令要执行前,保证Store1的写入操作对其它处理器可见

    sychronized/volatile在字节码、JVM、硬件OS层面实现细节

    - sychronized

    • 字节码层面

      sychronized m() : AccessFlag : ACC_VOLATILE

      sychronized(this){} : monitorenter monitorexit monitorenter

    • JVM层面

      C/C++ 调用操作系统的同步操作

    • 硬件OS层面

      X86 : lock cmpxchg / xxx

    -volatile

    • 字节码层面
      AccessFlag : ACC_VOLATILE
    • JVM层面

      volatile内存区域都加屏障

      StoreStoreBarrier volatile 写操作 StoreLoadBarrier

    ​ LoadLoadBarrier ​ volatile 读操作 ​ LoadStoreBarrier

    • 硬件OS层面

      windows lock 指令实现 或是 MESI实现

    面试new Object() 6连问

    • 1.解释对象的创建过程

      该问题结合上篇博客:JVM系列【3】Class文件加载过程不难回答出来。

      class loading

      class linking (vertification prepraration resolution)

      class initiazing

      new 申请内存空间

      成员变量赋初始值

      调用构造方法<init>:成员变量赋初始值;执行构造方法语句,super()父类构造。

    • 2.对象在内存中的存储布局

      对象在内存中布局分普通对象和数组对象。

      普通对象4部分:对象头markword(8字节)、ClassPointer指针(4或8字节)、实例数据、padding对齐为8的倍数。

      数组对象5部分,和普通对象类似,但中间是数组长度4字节和具体的数组数据。

      file

    • 3.对象头具体包括什么

      对象头markword(8字节)具体内容和对象锁状态有关系,其中最高位2位是锁状态中,最低3位用作锁标志位,中间4位是GC年龄,如下。

      file

    • 4.对象怎么定位

      通过句柄池和直接指针,具体参考:访问对象两种方式--句柄和直接指针

    • 5.对象怎么分配?

      对象的分配其实和垃圾回收GC有关系,后续总结GC详细讲。

      file

    • 6.Object o = new Object() 在内存中的占用多少个字节

      16个字节,根据第2点的内存布局可以算出。

    知识分享,转载请注明出处。学无先后,达者为先!
    查看原文

    赞 0 收藏 0 评论 0

    空虚碧海 发布了文章 · 10月10日

    JVM系列【3】Class文件加载过程

    JVM系列笔记目录

    • 虚拟机的基础概念
    • class文件结构
    • class文件加载过程
    • jvm内存模型
    • JVM常用指令
    • GC与调优

    Class文件加载过程

    JVM加载Class文件主要分3个过程:Loading 、Linking、Initialzing

    1.Loading

    Loading的过程就是通过类加载器将.class文件加载到jvm内存中过程。需要理解双亲委派机制、类加载器ClassLoader,加载过程如下。
    file

    #### ClassLoader

    不同的类加载器加载范围不一样,以Java8中的为例。

    • BootClassLoader 加载范围sun.boot.class.paht
    • ExtClassLoader 加载范围java.ext.dirs
    • AppClassLoader 加载范围java.class.path
    • CustomClassLoader 可自定义加载范围

    前三个加载器来自JDK的Launcher类,三个ClassLoader作为Launcher的内部类,感兴趣可以查看下源码。
    file
    开发者也可以自定义的ClassLoader,自定义记载范围。

    双亲委派机制

    自底向上检查该类是否已经加载,parent方向;自顶向下进行类的实际查找和加载,child方向。
    类的加载遵循双亲委派机制,主要是出于安全的考虑。双亲委派机制是如何实现的,下面源码会解释。
    file

    注意:双亲委派中存在所谓的父加载器并不是加载器的加载器,只是翻译的问题,别混淆了类的继承概念。

    ClassLoader源码

    file
    ClassLoader源码中比较重要的一个函数是loadClass(),执行过程是:findLoadedClass()->parrent.loadClass()->findClass(),第一步是自底向上查询是否已经加载,第二步是自顶向下查找加载类。这里就规定或是说实现了双亲委派机制。详细见ClassLoader的源码。

    自定义ClassLoader

    如何自定义ClassLoader?可以继承ClassLoader类,重新自己的findClass(),在里面调用defineClass()来实现自定义加载特定范围的类。

    如何打破双亲委派机制,哪种情形下打破过?

    从上面的ClassLoader源码中大概能看出是如何实现了双亲委派机制的,从这入手可以通过2种方式打破该机制:

    1. super(parent)指定parent会打破该机制
    2. 自定义ClassLoader重写loadClass()也可以打破

    何时打破过?双亲委派机制并不是不能打破,某些特殊场景下也会选择打破该机制。

    1. JDK 1.2之前,自定义ClassLoader必须重写loadClass(),打破过。
    2. 线程ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定。
    3. 热启动热部署,如tomcat都有自己模块指定的classloader,可以加载同一类库的不同版本。

    Class执行方式

    Class执行方式分为3种:解释执行、编译执行、混合执行,各有优缺点,可通过参数指定。

    • 1.解释执行:使用bytecode intepreter 解释器解释执行,该模式启动很快,执行稍慢,可通过-Xint参数指定该模式。
    • 2.编译执行:使用 Just in time Complier JIT编译器编译执行,该模式执行很快,编译很慢,可通过-Xcomp参数指定该模式。
    • 3.混合执行:默认的模式,解释器+热点代码编译,开始解释执行,启动较快,对热点代码进行实时监测和编译成本地代码执行,可通过-Xmixed参数指定该模式。
    热点代码监测:多次被调用的方法用方法计数器,多次被调用的循环用循环计数器,可通过参数-XX:CompileThreshold = 10000指定触发JIT编译的阈值。

    2.Linking

    Linking链接的过程分3个阶段:Vertification、Preparation、Resolution。

    • Vertification: 验证Class文件是否符合JVM规定。
    • Preparation:给静态成员变量赋默认值
    • Resolution:将类、方法、属性等符号引用解释为直接引用;常量池中的各种符号引用解释为指针、偏移量等内存地址的直接引用

    3. Initializing

    调用初始化代码clint,给静态成员变量赋初始值。

    这里可以了解下必须初始化的5种情况:

    • new getstatic putstatic invokestatic指令,访问final变量除外
    • java.lang.reflect对类进行反射调用时
    • 初始化子类的时候,父类必须初始化
    • 虚拟机启动时,被执行的主类必须初始化
    • 动态语言支持java.lang.invoke.MethodHandler解释的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化。

    4.总结思考

    设计模式中单例模式的双重检查的实现,INSTANCE是否需要加valatile

    public class Mgr06 {
        // 是否需要加volatile?
        private static volatile Mgr06 INSTANCE;
    
        private Mgr06() {
        }
    
        public static Mgr06 getInstance() {
            if (INSTANCE == null) {
                //双重检查
                synchronized (Mgr06.class) {
                    if(INSTANCE == null) {
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // new 了对象,不为null,但未完成变量的初始化复制,对象处于半初始化状                    态,其它线程有可能取到半初始化的对象。
                        INSTANCE = new Mgr06();
                    }
                }
            }
            return INSTANCE;
        }
    }

    个人认为是需要加的。思考方向, class文件load到内存,给静态变量赋默认值,再赋初始值,new 对象的时候,首先要申请内存空间,然后给成员变量赋默认值,接下来给成员变量赋初始值,这个过程中对象有可能处于半初始化状态,多线程并发下别的线程有可能取到半初始化的对象,加volatile可保证线程的可见性。

    知识分享,转载请注明出处。学无先后,达者为先!
    查看原文

    赞 0 收藏 0 评论 0

    认证与成就

    • 获得 0 次点赞
    • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

    擅长技能
    编辑

    (゚∀゚ )
    暂时没有

    开源项目 & 著作
    编辑

    (゚∀゚ )
    暂时没有

    注册于 5月14日
    个人主页被 142 人浏览