类加载顺序
  • 加载: 使用类加载器将class文件加载到内存中;
  • 验证: 验证class文件是否符合jvm的规范;
  • 准备: 为class类和里面的static变量准备内存空间, 并未一些类型赋予初始值, 比如int类型赋予0;
  • 解析: 将符号引用变为直接引用(待补充);
  • 初始化: 实例化一个对象, 这里是真正的为static变量以及static块中的数据赋值, 此时, 如果该类的父类不没有被加载, 会先加载父类, 初始化的时间, 则是该类被new的时候, 就是这个类被使用的时候;
  • 使用: 就是使用类执行代码逻辑;
  • 卸载: 卸载这个类;
类加载的双亲委派机制
  • 前提: 一个类被不同的类加载器加载, 会则会变为两个不同类, 因此一个类只能有一个唯一的类加载器加载;
  • 实现上述原则, 使用双亲委派模型:

    #启动类加载器: 
    Bootstrap ClassLoader, 加载jdk lib目录下在类;
    #扩展类加载器
    Extention classCloader, lib/ext目录下的类;
    #应用程序类加载器
    Application classLoader, classPath下的类;
    #自定义类加载器
    根据自己的需求去加载某些类;
    
    执行机制: 每个classLoader拿到类之后, 都不会自己去加载, 而是交给
    父加载器加载, 一层一层向上传递, 如果最后父类都不能加载, 则在向下
    传递, 如果都找不到, 就会抛出异常;
    
    jvm的分区
    元数据区
    用来存放类相关的数据, 比如我们加载的class文件;
    堆内存
    用来我们在执行程序的过程中创建的各种实例对象, 这是jvm优化最重要的部分;
    java虚拟机栈
    每一段代码的执行都是由一个线程执行的, 包括main函数, 也是由main线
    程, 因此每一线程都有自己的虚拟机栈, 来保存自己的局部变量, 方法也
    有自己的虚拟机栈, 当类执行完成, 或者方法执行完成, 这些局部变量就
    会从栈中弹出, 注意, 这里的局部变量只是一个引用, 而真正的实例是在
    堆内存中;
    程序计数器
    由于系统在执行程序的时候并不是一直在执行这个系统的功能, 而是cpu
    在不同的进程和线程中来回切换, 因此要有一个区域来记录这个代码执行
    到什么位置, 保证下次cpu来执行这段代码的时候直到从哪里开始, 程序
    计数器就是这个作用;
    其他数据区
    例如和NIO相关的数据区, socket网络相关的数据区;
    JVM的分代模型
    年轻代
      在jvm中绝大多数的类存在时间是很短的, 用完之后就直接卸载掉, 还有一部分类是要长期使用的, 比如我们的启动类, 以及启动类中的静态变量, 因此存在时间短的会保存在年轻代;
    老年代
    这里就是用来保存长期存活的对象的;
    永久代
    永久代就是上面提到的元数据区(jdk1.8以前叫方法区);
    对象在各个区域时如何流转的
      我们创建的对象, 会先进入年轻代, 随着程序的不断运行, 创建的对象越来越多, 年轻的空间会被逐渐占满, 此时就会触发一次Yong GC, 来回收此时已经不被使用的对象, 当这个对象躲过15次Yong GC还没被回收的时候, 这个对象就会被放进老年代;
    JVM参数解释
    -Xms: java堆内存大小;
    -Xmx: java堆内存最大大小;
    -Xmn: java堆内存新生代大小;
    -XX:MatespaceSize: 永久代大小;
    -XX:MatespaceSize: 永久代最大大小;
    -Xss: 每个线程栈内存大小;
    XX:MaxTenuringThreshold: 对象经历多次Yong GC之后进入老年代;
    -XX:PretenureSizeThreshold: 多大的对象直接进入老年代(默认字节);
    -XX:-HandlePromotionFailure: 这个参数用来在每次Yong GC之前检查老年代是否能存下年轻代现在所有的对象, 如果配置了这个参数, 即使存不下, 也不会触发老年代gc, 如果没有配置则会触发老年代gc(jdk1.6之后废弃);
    -XX:SurvivorRatio=8: 设置survivor区域进行动态年龄判断剩余空间百分比;
    -XX:UseParNewGC: 指定新生代使用ParNewGC垃圾回收器;
    -XX:ParallelGCThreads: 设置ParNew gc时的线程数, 一般不建议设置;
    -XX:CMSInitialtingOccupancyFaction: 定义老年代占用内存到了多少后就会自动触发垃圾回收, jdk默认是92%;
    -XX:UseConcMarkSweepGC: 老年代使用cms垃圾回收器;
    -XX:CMSFullGCsBeforeCompaction=0: 设置每次full gc之后进行内存碎片整理;
    -XX:+PrintGCDetils: 打印详细的gc日志;
    -XX:+PrintGCTimeStamps: 打印出来每次GC发生的时间;
    -Xloggc:gc.log: 将gc写入一个磁盘文件;
    -XX:CMSParallelInitialMarkEnabled: cms初识标记阶段开启多线程;
    -XX:CMSScavengeBeforeRemark: cms开始重新标记之前, 先执行一次yong gc;
    -XX:TraceClassLoading -XX:TraceClassUnloading: 可以用来跟踪类的加载和卸载;
    -XX:+DisableExplicitGC: 禁止使用代码调用gc

    可以用如下方式设置:
    image.png

    如何根据自己的系统配置jvm
  • 需要记录web应用的tps, 以及yong GC时间间隔和空间差值;
  • 这样就可以知道, 每秒钟的请求量, 以及每次GC时间间隔和空间, 使用公式: 每个请求平均占用空间=空间/(tps*GC);
  • 每个网站都有访问高峰(根据业务而定)或者公司规定的tps要达到的要求, 使用峰值tps*每个请求的空间=新生代空间大小
  • 调优的目的: 要是jvm不进行老年代垃圾回收, 同时新生代回收频率较低, 则要让所有的对象都在新生代保存, 这样能保证系统的稳定性;
  • 一般来说新生代和老年的空间大小是分配的相同的, 这样就可以估算出java堆内存的最小大小, 然后就可以根据自己的系统运行情况分配内存;
  • 对于永久代和栈内存, 没有可以参考的规范, 一般前者设置几百兆的空间, 后者设置1M左右的空间, 应该是足够的, 这些区域在有些情况下也会发生溢出现象, 根据实际情况调整;

    以上的只是估算, 而且只是一个大概的估算的方法, jvm调优是一个反复调试的过程, 而且每个系统在不同情况下的内存需求也不相同;

    通常都使用4核8G内存服务器, 可以应对大部分web应用;

    ##### 对象的引用
  • jvm使用可达性分析算法查找哪些对象还在被引用, 一般方法(方法未
    从栈区弹出)的局部变量和类的静态变量引用的对象都是不会被回收的;
  • 对象的引用分为强引用, 软引用, 弱引用和虚引用
    强引用: 根据1中来判断;
    软引用: 一般不会回收, 但是当内存空间不足时, 会将软引用回收;
    弱引用: 每次垃圾回收都会回收引用对象;

    ##### 垃圾回收算法
    > 复制算法(新生代)

    基本原理:
    将内存区域分为两部分, 每次使用的时候只向一个内存区域保存数
    据, 当一个内存区域要满的时候, 进行引用查询, 将还存活的对象保存到
    另一个区域中, 然后将原来的内存一次清空;
    缺点:
    内存的使用率太低, 因为每次垃圾回收的对象只是一小部分, 却需要
    一大块内存来保存;
    解决办法: 引入Eden区域和两块survivor区域
    survivor区域占内存的10%, Eden区域占新生代内存的80%, 每次存
    活的对象保存进其中一个survivor区域中;

    > 标记清理算法(老年代)

    就是标记处哪些对象存活, 哪些对象已经不被引用, 将不被引用的对象清
    理掉, 存活的对象移动到内存区域的集中位置, 避免内存碎片;

    ##### 对象什么时候进入老年代
  • 15次yang gc之后, 依然没有回收的对象进入老年代(可配置);
  • 动态年龄判断: survivor区域存放对象超过区域的50%, 那么此时大于等于survivor区域内对象年龄的对象会被放到老年代;
  • 大对象直接进入老年代;
  • Yong GC之后, 存活对象survivor区域不能保存;

老年代Full GC的触发条件

  • YongGC之前的检查, 如果每次yong gc之后存活对象的平均值大于老年代剩余空间, 触发;
  • 老年代空间不足以存放对象时触发;

    垃圾回收器分类
    ParNew:

用于新生代, serial是原始的新生代垃圾回收器, parNew比他好的地方是可以使用多核, 启动多个线程, 而serial只能使用单线程, 基于复制算法;

CMS:

用于老年代, 基于标记清理算法, 原理:

基本: 垃圾回收线程和系统工作线程同时进行;
1. 初识标记: 停止系统运行, 对老年代对象标记, 查看哪些已经不被引
用(只判断GC Root直接引用的对象), 虽然stop world, 但是运行快;
2. 并发标记: 系统继续运行, 对已经标记的对象进行深入跟踪(查看GC 
root引用还有哪些), 判断到底有没有被真正的引用, 比较耗时, 但系统
继续运行;
3. 重新标记: 停止系统运行, 对已经标记的和新加入的对象进行重新标
记, 速度快;
4. 并发清除: 系统运行, 对对象进行清除, 速度慢;

如果在上述阶段, 老年代的剩余内存不够用了, 就会触发Concurrent 
Mode Failure, 自动使用old serial垃圾收集器进行垃圾回收, 此时就
会造成系统长时间停顿;
G1: 大数据系统使用的垃圾回收器;
不同于ParNew+CMS, G1自己就可以搞定年轻代和老年代, 他将堆内存区
域划分为多个Region区域, 老年代和年轻代只是逻辑上的区分, 他最好的
地方是可以设置多久的时间内gc时间不能超多多少时间, 主要是通过标记
region区域中的垃圾对象, 判断回收这个region区域需要多久来做到的; 

region到底是属于新生代还是老年代, 其实在G1中是动态分配的, 这次是
新生代, 下次可能就是老年代了;

G1专有参数:
    -XX:UseG1GC: 使用g1垃圾回收器, region区域默认有2048个, 分配大小使用堆内存大小/2048;
    -XX:G1HeapRegionSize: 定义region大小, 大小只能是2的倍数, 1M, 2M, 4...;
    -XX:G1NewSizePercent: 新生代初始时占用多少内存;
    -XX:G1MaxNewSizePercent: 定义新生代最多占多少内存, 默认2%, 最大不超过60%;
    -XX:MaxGCPauseMills: 设置停顿最大时间;
    -XX:InitiatingHeapOccupancyPercent: 定义老年代占用多少内存时, 触发一次新生代和老年回收;
    -XX:G1MixedGCCountTarget: 定义最后一个阶段几次混合回收;
    -XX:G1HeapWastePercent: 回收时基于复制标记算法进行;
    -XX:G1MixedGCLiveThresholdPercent: 定义存活对象低于多少时才去回收这个region;

G1特性:
    新生代还是有eden和survivor区域的区分, 对于大对象也不同, 有专门
    的存放区域, 既不属于新生代, 也不属于老年代, 每次垃圾回收的时候都
    会去回收这个区域;
    
G1回收步骤:
    1. 初识标记
    2. 并发标记
    3. 最终标记
    4. 回收
为什么Yong gc比full gc速度快很多?
  1. 年轻代比老年代存活的对象要少很多;
  2. 老年代除了标记清除之外, 还要进行内存碎片的清理, 另外还有可能发生Concurrent Mode Failure;

    jvm命令使用
    jstat -gc PID
  3. S0C:这是From Survivor区的大小
  4. S1C:这是To Survivor区的大小
  5. S0U:这是From Survivor区当前使用的内存大小
  6. S1U:这是To Survivor区当前使用的内存大小
  7. EC:这是Eden区的大小
  8. EU:这是Eden区当前使用的内存大小
  9. OC:这是老年代的大小
  10. OU:这是老年代当前使用的内存大小
  11. MC:这是方法区(永久代、元数据区)的大小
  12. MU:这是方法区(永久代、元数据区)的当前使用的内存大小
  13. YGC:这是系统运行迄今为止的Young GC次数
  14. YGCT:这是Young GC的耗时
  15. FGC:这是系统运行迄今为止的Full GC次数
  16. FGCT:这是Full GC的耗时
  17. GCT:这是所有GC的总耗时

  18. jstat -gccapacity PID:堆内存分析
  19. jstat -gcnew PID:年轻代GC分析,这里的TT和MTT可以看到对象在年轻代存活的年龄和存活的最大年龄
  20. jstat -gcnewcapacity PID:年轻代内存分析
  21. jstat -gcold PID:老年代GC分析
  22. jstat -gcoldcapacity PID:老年代内存分析
  23. jstat -gcmetacapacity PID:元数据区内存分析
  24. jstat -gc pid 1000 10: 每个一秒打印一次, 打印10次;

    jmap -heap pid: 打印信息如下
    Heap Configuration:
    MinHeapFreeRatio         = 40
    MaxHeapFreeRatio         = 70
    MaxHeapSize              = 478150656 (456.0MB)
    NewSize                  = 10485760 (10.0MB)
    MaxNewSize               = 159383552 (152.0MB)
    OldSize                  = 20971520 (20.0MB)
    NewRatio                 = 2
    SurvivorRatio            = 8
    MetaspaceSize            = 21807104 (20.796875MB)
    CompressedClassSpaceSize = 1073741824 (1024.0MB)
    MaxMetaspaceSize         = 17592186044415 MB
    G1HeapRegionSize         = 0 (0.0MB)
    
    Heap Usage:
    New Generation (Eden + 1 Survivor Space):
    capacity = 9502720 (9.0625MB)
    used     = 883072 (0.8421630859375MB)
    free     = 8619648 (8.2203369140625MB)
    9.292834051724139% used
    Eden Space:
    capacity = 8454144 (8.0625MB)
    used     = 840504 (0.8015670776367188MB)
    free     = 7613640 (7.260932922363281MB)
    9.941917242005815% used
    From Space:
    capacity = 1048576 (1.0MB)
    used     = 42568 (0.04059600830078125MB)
    free     = 1006008 (0.9594039916992188MB)
    4.059600830078125% used
    To Space:
    capacity = 1048576 (1.0MB)
    used     = 0 (0.0MB)
    free     = 1048576 (1.0MB)
    0.0% used
    tenured generation:
    capacity = 20971520 (20.0MB)
    used     = 13647432 (13.015205383300781MB)
    free     = 7324088 (6.984794616699219MB)
    65.0760269165039% used
    
    jmap -histo pid: 打印信息如下, 类占用空间大小
    1543:             1             16  sun.reflect.GeneratedMethodAccessor33
    1544:             1             16  sun.reflect.GeneratedMethodAccessor4
    1545:             1             16  sun.reflect.GeneratedMethodAccessor5
    1546:             1             16  sun.reflect.GeneratedMethodAccessor6
    1547:             1             16  sun.reflect.GeneratedMethodAccessor7
    1548:             1             16  sun.reflect.GeneratedMethodAccessor8
    1549:             1             16  sun.reflect.GeneratedMethodAccessor9
    1550:             1             16  sun.reflect.ReflectionFactory
    1551:             1             16  sun.reflect.generics.tree.BottomSignature
    1552:             1             16  sun.reflect.generics.tree.IntSignature
    1553:             1             16  sun.reflect.generics.tree.VoidDescriptor
    1554:             1             16  sun.security.util.AlgorithmDecomposer
    1555:             1             16  sun.security.util.ByteArrayLexOrder
    1556:             1             16  sun.security.util.ByteArrayTagOrder
    1557:             1             16  sun.security.util.DisabledAlgorithmConstraints$Constraints
    1558:             1             16  sun.util.calendar.Gregorian
    1559:             1             16  sun.util.locale.provider.AuxLocaleProviderAdapter$NullProvider
    1560:             1             16  sun.util.locale.provider.CalendarDataUtility$CalendarWeekParameterGetter
    1561:             1             16  sun.util.locale.provider.SPILocaleProviderAdapter
    1562:             1             16  sun.util.locale.provider.TimeZoneNameUtility$TimeZoneNameGetter
    1563:             1             16  sun.util.resources.LocaleData
    1564:             1             16  sun.util.resources.LocaleData$LocaleDataResourceBundleControl
    1565:             1             16  websocket.drawboard.DrawboardContextListener
    jmap -dump:live,format=b,file=dump.hprof pid

在当前目录下生成存储快照

jhat -J-mx768m -port 7000 dump.hprof

使用浏览器查看存储快照, 访问ip:7000

发生OOM的区域
  • Matespace区域: 区域设置过小, 或者代码编写有问题, 加载了大量class
  • 虚拟机栈: 无限制的调用方法, 比如递归调用, 循环调用;
  • 堆内存: 即使在gc之后, 堆内存依然不够用;
#在发生oom时进行dump快照输出
-XX:+HeapDumpOnOutOfMemeryError
-XX:HeapDumpPath=/...

红番茄
7 声望2 粉丝

« 上一篇
rocketMQ笔记
下一篇 »
Spring笔记