JVM的小总结(转)
阅读原文时间:2023年07月08日阅读:1

  ref:http://www.cnblogs.com/ityouknow/p/6482464.html

注1:看了大神:纯洁的微笑的JVM系列篇,发现好多地方还是似懂非懂,理解的并不透彻,jvm的调优部分更是稀里糊涂;

    本片主要整理下jvm部分的知识点,方便以后的面试使用,(权且死记硬背了  !>_<!)

注2:由于不同的JVM厂商不同,关于JVM的分区,存在几个不确定的地方,以下简述对这些不甚明了的地方的个人理解,难免有误:

1、关于JVM的内存划分,“方法区”的叫法是JVM的规范中的术语,而Oracle公司的HotSpot虚拟机对方法区的实现是永久代(PerGen space),所以只有HotSpot才有永久带,也就有了对永久带的配置参数-XX:PermSize,网上的JVM的分析大多对于HotSpot的,但要明白“方法区”与“PerGen space”两者不是等价的。

2、JDK8中废弃了永久带(PermGen),启用了元空间(Metaspace)

     ref:https://www.cnblogs.com/yulei126/p/6777323.html

    ref:http://www.cnblogs.com/paddix/p/5309550.html

  废弃原因:

  • PermGen现实中经常爆OutOfMemoryError异常,hotspot中有参数实现PerGen空间的配置

  • oracle公司融合自身HotSpot JVM与 JRockit VM两种虚拟机,后者没有永久带,不需要配置;

  • (永久代会为 GC 带来不必要的复杂度,并且回收效率偏低)

    元空间Metaspace也是对方法区的实现,作用同以前的PermGen,其最大不同在于:Metaspace不在JVM中,而是使用本地内存(即JVM以外的本机内存)存放。因此,默认情况下Metaspace只收到本机内存的限制,同样也可以通过参数指定如:-XX:MetaspaceSize等。__

    JDK1.7中,就已经将PermGen中的部分数据转移到其他地方存储:如类的静态变量转移到Heap区,字符串也在Heap区???

<---------------------------------------------------------------------正片开始----------------------------------------------->

jvm的体系梳理                               

  • 类的加载机制

  • JVM的内存结构

  • GC算法 垃圾回收

  • GC分析 命令调优

      注:底部给出思维导图参考

1、类的加载机制

  1.1:什么是类的加载:

    (1)将类的.class文件以二进制读入到内存,放入运行时数据区的方法区,然后在堆区创建一个java.lang.Class对象,以封装类在方法区的数据结构。

    (2)类加载的最终产品是位于堆区的Class对象,该Class对象封装类在方法区的数据结构,并向JAVA程序员提供了访问方法区内数据解耦的接口。

  1.2:类的生命周期:

####: 类的生命周期:

#---- 类的加载

    |---- 装载(Loading)(为了区分加载,这里称其为装载):查找并导入类的Class文件,在堆区创建java.lang.Class的对象。

    *|---- 连接*过程分为三块:

      |---- 验证:文件的格式、元数据、字节码、符号引用验证,确保被装载的类的正确性,该阶段虽然重要但不是必须的;

      |---- 准备:为类的静态变量(static)分配内存,并将其初始化为默认值;如  public static int value=3 ,此时value的值是0而不是3;而对于:  public static final int value=3  ,该阶段value的值是3,而不是0,可以认为final static 常量在javac的编译期间,为value生成了ConstantValue属性,准备阶段jvm根据ConstantValue的值为value指定值。

      |---- 解析:将符号引用转换为直接引用;

     |---- 初始化:为类的静态变量,静态代码块等赋予正确的初始值。

   #---- 类的使用:new出对象供程序使用。

#---- 类的卸载:执行垃圾回收。

补充问题:

1.2.1:JVM的初始化步骤(初始化过程):

     1.2.2类的初始化时机:

     1.2.3:哪几种情况下,JVM会结束生命周期: 

      1、执行了system.exit();

      2、程序正常结束;

      3、执行过程中发生异常或错误而终止;

      4、操作系统的错误导致JVM的进程终止;

  1.3:类加载器

    

  • 启动类加载器:Bootstrap ClassLoader,负责加载JDK\jre\lib目录下或被-Xbootclasspath指定位置处,可悲jvm识别的类库;

  • 扩展类加载器:Extension ClassLoader,负责加载JDK\jre\lib\ext目录下或由java.ext.dirs系统变量所指定路径中的所有类库;开发者可以直接使用该加载器;

  • 应用程序加载器:ApplicationClassLoader,负责加载用户类路径(Classpath)所指定的类,开发者可以直接使用该类加载器;

      类的加载机制:

  • 全盘负责:某个类加载器负责加载某个Class,对该Class所依赖和引用的其他Class都由该加载器负责加载,除非显示指定。  

  • 父类委托(双亲委托机制):某个类加载器收到Class加载请求时,现将该请求转至父类加载器,对Class进行加载,父类找不到该类无法完成加载,才尝试自己加载。

  • 缓存机制:所有加载过的Class都会被缓存,当程序中需要使用某个类时,类加载器先从缓存区寻找该Class,不存在,才会读取Class的二进制,生成Class对象存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。

2、JVM内存结构

方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序计数器是运行是线程私有的内存区域

  • 堆Heap:存放实例对象,是JVM内存中最大的一块,也是GC的主要区域。

    • * 年轻代(8:1:1)

      *   Eden空间
      *   From Survivor空间
      *   To Survivor空间  
      • 老年代 
  • 方法区Method Area:存放被jvm加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

  • 程序计数器(Program Counter Register):是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。

  • 栈:

    • * JVM栈:描述方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
      • 本地方法栈(Native Method Stacks):同JVM栈,只不过它是为JVM的Native方法服务;

对象分配规则:

注:相关Minor GC、Major GC&Full GC参见:

ref1:http://www.importnew.com/15820.html :这是翻译的外国人的总结,感觉有些生硬;

ref2 :  http://www.cnblogs.com/hnrainll/p/3410042.html :来自博客园,解释的很好,简单明了;

  说明:

  1. 对象优先分配在Eden区,Eden区是连续的内存空间,如果Eden去没有足够的空间,JVM进行一次minor GC或Young GC(即从年轻代回收内存),将剩余的对象复制到一个Survivor区

  2. 大对象直接进入老年代(需要大量连续内存空间的对象),以避免在Eden和两个survivor区间的大量内存拷贝。(新生代采用复制算法收集内存)

  3. 长期存活的对象进入老年代。JVM为每一个对象定义了一个年龄计数器,如果经过1次minor GC后,则进入survivor区,之后每一次Minor GC,该对象的年龄加1,直到达到阈值进老年区。

  4. 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,那年龄大于或等于该年龄的对象进入老年区。

  5. 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区剩余值的大小,则进行一次Full GC;如果小于检查HandlePromotionFailure设置,如果出现true则只进行Monitor GC,如果false,则进行Full GC。

       注:另外网上还有另一种回答:

  6. Survivor切换:每次的Minor GC都会将剩余对象添加到该Survivor区,当该Survivor满了以后,将其中活着的复制进另一个Survivor区,以后的Minor GC将或者的对象添加到切换后的Survivor区,两个Survivor总有一个是空的

  7. 进入老年代:当在两个Survivor间切换了几次(HotSpot默认15次),将其中仍然存活的对象,复制到老年代。

  8. Full GC:老年代空间一般较大,当老年代内存不足时,出发Major GC.(Full GC与Major GC的区别网上不清楚,参照ref1

各个内存区域的控制参数大小:

  • -Xms 设置堆的最小空间;

  • -Xmx设置堆的最大空间大小;    

  • -XX:NewSize设置新生代最小空间大小。

  • -XX:MaxNewSize设置新生代最大空间大小。

  • -XX:PermSize设置永久代(方法区)最小空间大小。(JDK1.8废弃PermGen

  • -XX:MaxPermSize设置永久代(方法区)最大空间大小

  • -Xss设置每个线程的堆栈大小。

  • 老年代的空间设置:Xms(Xmx)-XX:NewSize(MaxNewSize)进行设置;

    3、GC算法 垃圾回收:

3.1、对象存活条件判断:
  • * 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,释放时减1,为0时回收。但无法解决对象相互循环引用的问题;

    • 可达性分析:从GC roots开始向下搜索,搜索走过的路径称为引用链。当一个对象到GC roots没有任何引用链相连时,证明此对象是不可用的,不可达对象。

        3.2、GC 算法:

  • * 标记-清除算法(Mark-Sweep) :第一阶段标记处所有需要回收的对象,第二阶段统一回收所有被标记的对象。

    • 复制算法(Copying)      :将可用内存按容量大小划分为相等的两块,每次只使用一块。当前块用完了,则将还活着的对象复制到另外一块上,并把当前块内存清空。

    • 标记-压缩算法                        :标记过程同Mark-Sweep算法,标记完成后,让所有存活的对象向一端移动,清理掉端边界以外的内存。

    • 分代收集(Generational Collection):将JAVA堆分为年轻代和老年代,分别采用不同的收集算法(年轻代:复制;老年代标记整理)。

        3.3、GC回收器

    垃圾回收器是对GC算法的实现:

  • Serial收集器:是最古老、最稳定、效率高的收集器。参数控制:-XX:+UseSerialGC

    • 单个线程串行GC
    • 新生代复制算法、老年代标记压缩
    • GC过程中会Stop-the-world
  • ParNew收集器:是Serial收集器的多线程版本;参数控制:-XX:+UseParNewGC  ParNew收集器 -XX:ParallelGCThreads 限制线程数量。

    • 新生代并行GC,老年代串行GC
    • 新生代复制算法、老年代标记压缩;
    • GC过程会Stop-the-world。
  • Parallel收集器:类似于ParNew,更关注系统的吞吐量(用户代码运行时间/总运行时间)

    • 新生代复制算法、老年代标记压缩;
    • 老年代串行;
  • Parallel Old收集器:参数控制: -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行

    • **多线程+标记-整理算法
      **
  • CMS收集器:以获取最短的回收停顿时间为目标。

  • 优点:并发收集、低停顿。

  • 缺点:大量空间碎片,并发降低吞吐量。

  • 步骤:

4、GC分析 命令调优

  JVM调优就是根据GC的日志分析JVM的内存分配、回收的情况来调整各区域的内存比例或GC回收的策略。???

  4.1、GC日志分析

young gc日志格式:

    full gc日志格式:

 4.2、JVM调优命令

    运用jvm自带的命令,可以方便的在生产监控和打印堆栈日志信息中帮助开发者定位问题。

    Sun的JDK监控和故障处理命令主要有以下:

  • * jps:JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。
    • jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
    • jmap,JVM Memory Map命令用于生成heap dump文件
    • jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
    • jstack,用于生成java虚拟机当前时刻的线程快照。
    • jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。

4.1.1:jps(JVM process status tool)显示指定系统内所有HotSpot的进程。格式:

        jps [options] [hostid]

    - l :输出主类全名或jar路径;  
    -q :只输出LVMID;  
    -m:输出jvm启动时传递给main()的参数;  
    - v:输出jvm启动时显示指定的jvm启动参数;  

4.1.2:jstat(JVM statistics Monitoring)用于监视虚拟机运行时状态信息的命令,可以显示虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据;

   jstat [option] LVMID [interval] [count] : 操作参数 本地虚拟机进程ID 连续输出时间间隔 连续输出的次数

    jstat -gc 1262 2000 20 /*每隔2000ms输出1262的gc情况,输出20次  
-class : 显示ClassLoader的行为统计;
-compiler:输出JIT编译过的方法数量耗时;
-gc :垃圾回收堆的行为统计;
-gccapacity:输出堆的各区域的最大最小空间;
-gcutil :输出堆的各区域已用空间占总空间的百分比;
-gccause :垃圾收集统计概述;
-gcnew :统计新生代行为;
-gcnewcapacity:统计新生代的内存空间;
-gcold :统计旧生代行为;
-gcoldcapacity :统计旧生代的大小和空间;
-gcpermcapacity:永生代的行为统计;
printcompilation :hotspot编译方法统计;

4.1.3:jmap(jvm memory map)用于生成heap dump文件(进程所使用内存情况的一次快照)。

       jmap [option] LVMID

4.1.4:jhat(JVM heap analysis tool)与jmap搭配,分析jmap生成的dump;

4.1.5:jstack :jstack用于生成java虚拟机当前时刻的线程快照;

4.1.6:jinfo:实时查看和调整虚拟机的运行参数。

  4.3、调优工具:

    常用调优工具分为两类,

    jdk自带监控工具:jconsolejvisualvm(jdk/bin目录下)

    第三方有:MAT(Memory Analyzer Tool)、GChisto

  • * jconsole:Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控
    • jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等
    • MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
    • GChisto,一款专业分析gc日志的工具

<---------------------------------------------------------------------------------------------------------->