JVM 理解性学习(一)

重新学习,重新理解

1、类加载过程等

  验证:.class 文件加载到 JVM 里的时候,会验证下该文件是否符合 JVM 规范。

  准备:给实体类分配内存空间,以及给类变量(static 修饰)分配"默认值"。

  解析:将符号引用替换为直接引用。

  初始化:将类初始化,如果有父类且父类未初始化,会先初始化父类,再初始化此类。然后再对各个变量赋值。

  一个线程一个虚拟机栈,一个方法对应一个栈帧,栈帧里存了局部变量等信息。

  程序计数器主要是记录当先线程执行到的字节码位置。

2、JVM 参数设置

  java -Xms512M -Xmx512M -Xmn256M -Xss1M -XX:PermSize=128M -XX:MaxPermSize=128M -jar App.jar

  堆内存和最大内存512M,新生代256M,栈内存1M,元数据内存和最大内存128M。

3、四种引用

  引用:一个引用指向一个堆中的对象。

  强引用:还有局部变量或者静态类变量等指向对象,此时该对象不可被回收(任意情况)。

  软引用:如果堆中新生代内存满了,发生 minorGC,就会回收该软引用。

4、新生代3区的思想

  将新生代分为3个区 Eden 区,2个 Survivor 区。用到了复制算法,而一般情况下,MinorGC 后存活的对象只有垃圾回收之前的1%。 所以三个区的比例一般是8:1:1。

  当 Eden 区对象占满了内存,触发 MinorGC ,就会把存活对象放在一个 Survivor 区,然后清除 Eden 区。

  当发生第二次 MinorGC 时,就会把 Eden 区和存了对象的 Survivor 区的存活对象,放到第二个 Survivor 区。然后清除 Eden 区和第一个Survivor 区的死亡对象。

5、新生代对象进老年代对象的几种情况

  1)、躲过15次 minorGC 之后从新生代进入老年代;

  2)、动态年龄判断:当前放对象的 Survivor 区,相同年龄的一批对象的总大小大于该区的内存的50%,大于该年龄的其他老对象,就会进入老年代;

  3)、大对象直接进入老年代。有一个 JVM 参数 '-XX:PretenureSizeThreshold' 设置值为字节数,创建超过该大小的对象直接进入老年代。

  4)、Eden 区存活对象太多,超过了 Survivor 的大小,就直接把这些对象都转移到老年代去。(空间担保机制)

  老年代内存分配担保规则:在执行一次 MinorGC 之前,JVM 会先检查一下老年代可用的内存空间,是否大于新生代所有对象的总大小。

  触发 FullGC:

  Survivor 区域放不下每次 MinorGC 后存活的对象,所以直接放入了老年代,以至于没发生几次 MinnoGC 后,就会触发 FullGC。

  假如老年代可用内存小于新生代所有对象的大小,就会检查一个参数是否设置。设置后,遵循老年代空间分配担保原则,就会检查老年代的内存大小,是否大于之前 MinorGC 后进入老年代的对象的平均大小。

  如果判断失败,或者没有设置参数,就会触发 FullGC。

  尽量少 FullGC 思路:

    1)、将对象在新生代存活年龄又15变大,改为30或更大;

    2)、禁止对象动态年龄,防止对象还未活到30次 MinorGC 就移到老年代;

    3)、将新生代占的内存空间调大,并且将 Surivivor 区域调大。保证每次 MinorGC 后存活的对象能放在 Surivvor 区域,不会因为 Survivor 区域过小,直接将对象放老年代。

6、ParNew 和 CMS 垃圾回收器

  多线程并发的机制,性能更好,一般是线上生产系统的标配。

  JVM 优化,指 减少垃圾回收的频率,降低垃圾回收的时间,减小垃圾回收对系统运行的影响。

  Stop th World :在垃圾回收的时候,停止系统的运行,停止对象的创建。

  ParNew:多线程垃圾回收机制。在合适的时机执行 MinorGC,停止程序运行,禁止程序继续创建对象,用多个垃圾回收线程去回收垃圾对象。(多线程是为了适应 多核 CPU)

  指定参数: XX:+UsePerNewGC ,JVM 启动,新生代就是用 PerNew 垃圾回收器了。

  CMS:标记清理算法,采取的是垃圾回收线程和系统工作线程,尽量同时执行的模式来处理的。

  分为四个阶段:初始标记、并发标记、重新标记、并发清理。

  初始标记:让系统的工作线程全部停止,进入 "Stop the World" 状态。去标记 GC Roots,方法的局部变量和类的静态变量是 GC Roots,类的实例变量不是 GC Roots。(暂停工作线程的速度很快,只是为了标记 GC Roots 直接引用的变量) (标记活着的---有引用的对象

  并发标记:对老年代所有的对象都进行 GC Roots 追踪,是最耗时的。追踪所有对象是否从根源上被 GC Roots 引用了。但是最耗时的垃圾回收线程,是和系统运行的线程并发进行,所以不影响系统。(因为老年代大多数对象都是存活的)

  重新标记:让系统停下来,进入 "Stop the World" 阶段。重新标记下第二阶段新创建的对象,还有一些已有对象可能失去引用,变成垃圾对象。(再找到活着的少部分,其他都是垃圾对象

  并发清理:很耗时,清理之前标记为垃圾的对象即可。(之前标记了存活和垃圾)垃圾回收线程,也是和系统运行线程并发执行。

  真正耗时的第二和第四阶段,是和工作线程并发进行。第一和第三阶段,虽然停止了工作线程,但是工作内存不多,不太耗时。

  CMS 并发垃圾回收机制会有几个问题:

    第一个问题:消耗 CPU 资源。

    第二个问题:在垃圾回收的时候,会新产生浮动垃圾对象,如果该对象占内存超过了老年代预留的内存,就会发生 Concurrent Mode Failure ,并发失败,执行 Serial Old 垃圾回收器,强行 Stop th World。

    第三个问题:内存碎片问题。Full GC 后再次 Stop the World ,停止工作线程,整理碎片。把存活对象移到一起。

  所以为什么 FullGC 比 MinorGC 要慢10倍还多,主要就是算法的不同,以及 FullGC 后还进行内存整理。

  -XX:CMSInitiatingOccupancyFaction 参数可以设置老年代中对象占多少比例的时候,出发 CMS 垃圾回收器的回收机制,默认92%。

 

猜你喜欢

转载自www.cnblogs.com/AlmostWasteTime/p/11258761.html