JVM学习整理

HotSpot VM使用分代垃圾收集器基于以下两个观点事实:

1.大多数分配对象的存活时间都很短。

2.存活时间久的对象很少引用存活时间短的对象。

基于此假设, HotSpot VM将堆分成2个物理区(也称为空间),这就是分代。

新生代。大多数创建的对象都会被分配到新生代,与整个Java堆相比,通常新生代的空间较少并且收集频繁。新生代中大多数对象的存活时间都比较短。所以通常来说,新生代收集也被称为次要收集(Minor GC),并且收集的效率也较高。

        新生代分为3个独立空间:

        1.Eden:大多数新对象都分配在该空间(大对象可能会直接分配在老年代)。Minor GC侯Eden区几乎就是空的。

        2.Survivor(1对):这里存放的对象至少经历了1次Minor GC,他们在提升到老年代之前,还有一次被收集的机会。它们充当了交换的角色。在垃圾收集的过程中,可能存在1个Survivor不足以容纳Eden和另一个Survivor中的存活对象。如果Survivor中的对象溢出,多余的对象会被移到老年代。这称为过早提升(Premature Promotion),这会导致老年代中短期存活对象的增长,可能会引起严重的性能问题。再进一步说,在Minor GC过程中,如果老年代满了,可能会引起严重的Full GC事件,这将导致遍历整个Java堆。这被称为提升失败(Promotion Failure)。

老年代。新生代中长期存活的对象会被提升(Promote)或晋升(Tenure)到老年代中。通常来说,老年代的空间比新生代大,而空间增长的速度比新生代慢。因此,相比Minor GC而言,老年代收集的执行效率比较低。但是一旦发生,执行时间也较长。

永久代。虽然称之为代,但是其实并不能作为代来区分。其主要用于存储元数据,例如类的数据结构、保留字符串(Interned String)等。

      JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中,并且 JDK 1.8 中已经不存在永久代。并且1.8中将元数据放到本地内存中,另外将静态变量和常量池放到了Java堆中。

       Java 1.8和1.7有一个很大的不同就是HotSpot 已经没有 “PermGen space”这个区间了,取而代之是一个叫做 Metaspace(元空间) 的东西。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:     

       采用这种方式的优势:

       各个项目共享同样的class内存空间,比如两个项目都用到了fast-json开源包,在meatspace中只存一份class,提高内存的利用率,更有利于内存回收

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

 

       Java虚拟机用了一个叫做CardTable(卡表)的数据结构来标记老年代的某一块内存区域中的对象是否持有新生代对象的引用,卡表的数量取决于老年代的大小和每张卡对应的内存大小,每张卡在卡表中对应一个比特位,当老年代中的某个对象持有了新生代对象的引用时,JVM就把这个对象对应的Card所在的位置标记为dirty(bit位设置为1),这样在Minor GC时就不用扫描整个老年代,而是扫描Card为Dirty对应的那些内存区域。

      这样子可以提高效率减少MinorGC的停顿时间。

      在JVM中,一个Card的大小是512字节,在多个线程并行收集时,JVM通过ParGCCardsPerStrideChunk参数设置每个线程每次扫描的Card数量,默认是256,相当于是把老年代分成许多strides,每个线程每次扫描一个stride,每个stride大小为512*256 = 128K,如果你的老年代大小为4G,那总共有4G/128K=32K个Strides。多线程在扫描这么多的strides时就涉及到调度和分配的问题,stride数量太多就会导致线程在stride之间切换的开销增加,进而导致GC暂停时间增长。因此JVM提供了ParGCCardsPerStrideChunk这个参数来配置每个stride对应的card数量,这个数量要根据实际的业务场景进行调优,网上一般流传3个魔术数字:32768、4K和8K.  

-XX:+UnlockDiagnosticVMOptions
-XX:ParGCCardsPerStrideChunk=4096

       这个值不能设置的太大,因为GC线程需要扫描这个stride中老年代对象持有的新生代对象的引用,如果只有少量引用新生代的对象那就导致浪费了很多时间在根本不需要扫描的对象上。

参考文档:

  1. Secret HotSpot option improving GC pauses on large heaps
  2. Advanced JVM and GC tuning

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值