jvm入门(3)

1.垃圾回收概念及其算法:
谈到垃圾回收(Garbage Collection,简称GC),需要先澄清什么是垃圾,类比日常生活中的垃圾,我们会把他们丢入垃圾桶,然后倒掉。GC中的垃圾,特指存于内存中、不会再被使用的对象,而回收就是相当于把垃圾“倒掉”。
垃圾回收有很多种算法:如引用计数法、标记压缩法、复制算法、分代、分区的思想。

2.垃圾收集算法
(1)引用计数法:
引用计数法:这是个比较古老而经典的垃圾收集算法,其核心就是在对象被其他所引用时计数器加1,而当引用失效时则减1,但是这种方式有非常严重的问题:无法处理循环引用的情况、还有就是每次进行加减操作比较浪费系统性能。
(2)标记清除法:
标记清除法:就是分为标记和清除俩个阶段进行处理内存中的对象,当然这种方式也有非常大的弊端,就是空间碎片问题,垃圾回收后的空间不是连续的,不连续的内存空间的工作效率要低于连续的内存空间。
(3)复制算法:
复制算法:其核心思想就是将内存空间分为两块,,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中去,之后去清除之前正在使用的内存块中所有的对象,反复去交换俩个内存的角色,完成垃圾收集。(java中新生代的from和to空间就是使用这个算法)
(4)标记压缩法:
标记压缩法:标记压缩法在标记清除法基础之上做了优化,把存活的对象压缩到内存一端,而后进行垃圾清理。(java中老年代使用的就是标记压缩法)

为什么新生代和老年代使用不同的算法?
分代算法:就是根据对象的特点把内存分成N块,而后根据每个内存的特点使用不同的算法。
对于新生代和老年代来说,新生代回收频率很高,但是每次回收耗时都很短,而老年代回收频率较低,但是耗时会相对较长,所以应该尽量减少老年代的GC.
分区算法:其主要就是将整个内存分为N多个小的独立空间,每个小空间都可以独立使用,这样细粒度的控制一次回收都少个小空间和那些个小空间,而不是对整个空间进行GC,从而提升性能,并减少GC的停顿时间。

3.垃圾回收时的停顿现象:
垃圾回收器的任务是识别和回收垃圾对象进行内存清理,为了让垃圾回收器可以高效的执行,大部分情况下,会要求系统进入一个停顿的状态。停顿的目的是终止所有应用线程,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态在某一个瞬间的一致性,也有益于更好低标记垃圾对象。因此在垃圾回收时,都会产生应用程序的停顿。

4.对象如何进入老年代:
一般而言对象首次创建会被放置在新生代的eden区,如果没有GC介入,则对象不会离开eden区,那么eden区的对象如何进入老年代呢?一般来讲,只要对象的年龄达到一定的大小,就会自动离开年轻代进入老年代,对象年龄是由对象经历数次GC决定的,在新生代每次GC之后如果对象没有被回收则年龄加1。虚拟机提供了一个参数来控制新生代对象的最大年龄,当超过这个年龄范围就会晋升老年代。
-XX:MaxTenuringThreshold,默认情况下为15。
测试代码如下:

public class Test05 {
    public static void main(String[] args) {
        //初始的对象在eden区
        //参数:-Xmx64M -Xms64M -XX:+PrintGCDetails
        for(int i=0; i< 5; i++){
            byte[] b = new byte[1024*1024];
        }
    }
}

运行结果如下:
Heap
PSYoungGen total 19456K, used 6134K [0x00000000fea80000, 0x0000000100000000, 0x0000000100000000)
eden space 16896K, 36% used [0x00000000fea80000,0x00000000ff07d860,0x00000000ffb00000)
from space 2560K, 0% used [0x00000000ffd80000,0x00000000ffd80000,0x0000000100000000)
to space 2560K, 0% used [0x00000000ffb00000,0x00000000ffb00000,0x00000000ffd80000)
ParOldGen total 44032K, used 0K [0x00000000fbf80000, 0x00000000fea80000, 0x00000000fea80000)
object space 44032K, 0% used [0x00000000fbf80000,0x00000000fbf80000,0x00000000fea80000)
PSPermGen total 21504K, used 2515K [0x00000000f6d80000, 0x00000000f8280000, 0x00000000fbf80000)
object space 21504K, 11% used [0x00000000f6d80000,0x00000000f6ff4f48,0x00000000f8280000)

可以发现,初始创建的对象都在eden区。
根据设置MaxTenuringThreshold参数,可以指定新生代对象经过多少次回收后进入老年代。

另外,大对象(新生代eden区无法装入时,也会直接进入老年代)。JVM里有个参数可以设置对象的大小超过在指定的大小之后,直接晋升老年代。
-XX:PretenureSizeThreshold
测试代码如下:

public class Test06 {

    public static void main(String[] args) {

        //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000
        Map<Integer, byte[]> m = new HashMap<Integer, byte[]>();
        for(int i=0; i< 5; i++){
            byte[] b = new byte[1024*1024];
            m.put(i, b);
        }
    }
}

测试结果如下:
Heap
def new generation total 9216K, used 819K [0x00000000f9000000, 0x00000000f9a00000, 0x00000000f9a00000)
eden space 8192K, 10% used [0x00000000f9000000, 0x00000000f90ccfb8, 0x00000000f9800000)
from space 1024K, 0% used [0x00000000f9800000, 0x00000000f9800000, 0x00000000f9900000)
to space 1024K, 0% used [0x00000000f9900000, 0x00000000f9900000, 0x00000000f9a00000)
tenured generation total 20480K, used 5136K [0x00000000f9a00000, 0x00000000fae00000, 0x00000000fae00000)
the space 20480K, 25% used [0x00000000f9a00000, 0x00000000f9f04060, 0x00000000f9f04200, 0x00000000fae00000)
compacting perm gen total 21248K, used 2519K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 11% used [0x00000000fae00000, 0x00000000fb075d20, 0x00000000fb075e00, 0x00000000fc2c0000)
No shared spaces configured.

可以发现tenured的使用为5136K,差不多为5M,即对象在老年代。

另外一种情况:

public class Test06 {

    public static void main(String[] args) {

        //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000
        //这种现象原因为:虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此就失去了在老年代分配的机会
        //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000 -XX:-UseTLAB
        Map<Integer, byte[]> m = new HashMap<Integer, byte[]>();
        for(int i=0; i< 5*1024; i++){
            byte[] b = new byte[1024];
            m.put(i, b);
        }
    }
}

运行结果如下:
Heap
def new generation total 9216K, used 6424K [0x00000000f9000000, 0x00000000f9a00000, 0x00000000f9a00000)
eden space 8192K, 78% used [0x00000000f9000000, 0x00000000f9646388, 0x00000000f9800000)
from space 1024K, 0% used [0x00000000f9800000, 0x00000000f9800000, 0x00000000f9900000)
to space 1024K, 0% used [0x00000000f9900000, 0x00000000f9900000, 0x00000000f9a00000)
tenured generation total 20480K, used 16K [0x00000000f9a00000, 0x00000000fae00000, 0x00000000fae00000)
the space 20480K, 0% used [0x00000000f9a00000, 0x00000000f9a04010, 0x00000000f9a04200, 0x00000000fae00000)
compacting perm gen total 21248K, used 2523K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 11% used [0x00000000fae00000, 0x00000000fb076cd8, 0x00000000fb076e00, 0x00000000fc2c0000)
No shared spaces configured.

可以发现这里的tenured老年代区只使用了16K,这种现象原因为:虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此就失去了在老年代分配的机会。

5.TLAB区域:
TLAB全称是Thread Local Allocation Buffer 即线程本地分配缓存,从名字上看是一个线程专用的内存分配区域,是为了加速对象分配而生的。每一个线程都会产生一个TLAB,该线程独享的工作区域,java虚拟机使用这种TLAB区来避免多线程冲突问题,提高了对象分配的效率。TLAB空间一般不会太大,当大对象无法在TLAB分配时,则会直接分配到堆上。
-XX:+UseTLAB 使用TLAB
-XX:+TLABSize 设置TLAB大小
-XX:TLABRefillWasteFraction 设置维护进入TLAB空间的单个对象大小,他是一个比例值,默认为64,即如果对象大于整个空间的1/64,则在堆创建对象。
-XX:+PrintTLAB 查看TLAB信息
-XX:ResizeTLAB 自调整TLABRefillWasteFraction阀值。

6.对象创建流程图:
这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值