Java——JVM学习笔记

最美的不是下雨天,是曾与你躲过雨的屋檐————《不能说的秘密》

JVM双亲委派机制

Java——JVM学习笔记
好处:防止内存中出现了多份相同的字节码
当一个.class文件要被加载进JVM的时候

  1. AppClassLoader首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成
  2. 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成
  3. 如果BootStrapClassLoader加载失败,会使用ExtClassLoader来尝试加载;若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,若AppClassLoader还是加载失败,会抛出异常ClassNotFoundException

当成功加载好某个类时,会将这个类缓存起来,下次使用时直接使用缓存,不会再次加载

类加载过程

加载器加载到jvm中,步骤:

  1. 加载,通过类名查找并加载该类的二进制流,在堆中生成该类的class类对象

加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了

  1. 连接
    a. 验证,文件格式验证→元数据验证→字节码验证→符号引用验证
    b. 准备,为类变量分配内存并设置类变量初始值的阶段,这里指的事类的静态变量,不包括实例变量
    c. 解析,将常量池的符号引用代替为直接引用

    JIT即时编辑器

    JVM解析过程:

    对热点代码进行重新编译优化,生成机器代码,让CPU直接执行,非热点代码直接解析

    热点代码: 多次调用的方法,多次执行的循环体
    使用热点探测来检测是否为热点代码:采样和计数器,jvm使用的是计数器,当计数器超过阈值溢出了,就会触发JIT编译

  2. 初始化

Java——JVM学习笔记
图片来源:https://segmentfault.com/a/11...

JVM内存结构

基于JDK1.8
Java——JVM学习笔记

  1. 堆:线程共享,内存中最大的一块,存放对象实例,基本所有对象实例以及数组都在这里分配内存,是垃圾收集器的主要区域, 现在收集器基本采用分代垃圾收集算法,所有堆又分为新生代和老年代
  2. 虚拟机栈:线程私有,生命周期和线程相同,描述的是Java方法执行的内存结构,每次方法调用的数据通过栈传递,所以每个方法被执行时都会同时创建一个栈帧
  3. 本地方法栈:线程私有,与虚拟机栈类似,区别是虚拟机栈为Java方法执行服务,本机方法栈为虚拟机使用到的Native方法服务
  4. 程序计数器:线程私有,是当前线程所执行的字节码的行号指示器,因此每条线程都有一个独立的程序计数器
  5. 方法区:线程共享的内存区域,用于储存被虚拟机加载的类信息、常量、静态变量等数据

常量池:

主要针对String
https://tech.meituan.com/2014...

JVM垃圾回收——GC

JVM垃圾回收主要针对的是内存结构中的堆

Java——JVM学习笔记

JVM回收的是"垃圾",即是程序不再使用了,不再需要了,因此回收的第一步需要判断哪些是垃圾,即对象死亡
常用有:**引用计数法**和**可达性分析算法**

引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的

可达性分析算法:主流的JVM采用的是这种方式,这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。能够做GC Roots的有:

- 虚拟机栈中引用的对象(局部变量)
- 方法去中类静态属性引用的对象(static对象实例)
- 方法区中常量引用的对象(常量实例)
- 本地方法引用的对象

引用:强引用、软引用、弱引用和虚引用

  • 强引用:最普遍的引用,如Object object = new Object()就是强引用,即使内存不足也不会回收这类引用,抛出OutOfMemory的异常
  • 软引用:软引用引用的对象,在垃圾回收时,如果发现内存资源不足,即使被引用了依然会被回收,但是在内存资源充足的情况下是不会回收的
  • 弱引用:弱引用引用的对象,在垃圾回收时,不管内存资源充不充足,都会被回收
  • 虚引用:形同虚设的引用,在任何时候都可能被垃圾回收

不可达的对象并非一定回收
如果一个不可达的对象覆盖了finalize方法,并且这个方法从来没被执行过,垃圾回收器就不会马上回收,而是放在一个队列,进行二次标记,但在下一次回收前,重新建立引用,便不会被回收。

**不推荐在finalize方法中去释放资源,因为它什么时候会被调用是不确定的。**

垃圾回收算法

  • 标记-清除算法
  • 复制算法
  • 标记-整理算法
  • 分代收集算法

分代收集算法
将 java 堆分为新生代和老年代,根据各个年代的特点选择合适的垃圾收集算法

  • 新生代(Minor GC):复制算法 ,所以分为Eden区、Survivor1、Survivor2三个区
  • 老年代(Full GC):标记-清除算法 或标记-整理算法

垃圾收集器

算法只是方法论,收集器才是干活的

  • Serial收集器
  • ParNew收集器
  • Parallel Scavenge收集器
  • Serial Old收集器
  • Parallel Old收集器
  • CMS收集器
  • G1收集器

根据具体应用场景选择适合自己的垃圾收集器

什么情况下触发垃圾回收

  • Minor GC:当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
  • Full GC:对整个堆进行整理,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。触发Full GC的原因有几个:

    • 老年代被写满
    • 持久带被写满
    • System.gc()被显示调用
    • 上一次GC之后Heap的各域分配策略动态变化

JVM参数与调优

在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节

  • 年轻代:

    1. 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
    2. 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
  • 老年代:

    1. 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:

      1. 并发垃圾收集信息
      2. 持久代并发收集次数
      3. 传统GC信息
      4. 花在年轻代和年老代回收上的时间比例
      5. 减少年轻代和年老代花费的时间,一般会提高应用的效率
    2. 吞吐量优先的应用
      一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
**较小堆引起的碎片问题**

因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:

1. -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
2. -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩

常见配置汇总

堆设置

-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小

收集器设置

-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器

垃圾回收统计信息

-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename

并行收集器设置

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

并发收集器设置

-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

学习链接:

https://segmentfault.com/a/11...
https://snailclimb.top/JavaGu...
感谢两位大佬的文章,受益匪浅

jvm

相关推荐