JVM垃圾收集算法:垃圾收集器主要关注Java堆和方法区

程序计数器、虚拟机堆栈和本地方法。这三个区域随着线的诞生和毁灭。栈中的栈帧是用方法进入的。并有序退出,进行弹出、推送操作。每个栈帧分配多少内存在类结构确定之后就基本知道了。因此,这些区域的内存分配和回收是确定性的。在这些领域,不需要考虑太多的回收问题。垃圾收集器主要关注Java堆栈和方法区。
1。如何判断对象是否还活着?
首先我们来说说为什么要判断是否存活。垃圾收集器回收堆之前,首先要判断哪些对象仍然被引用或者稍后将被引用,即。它们是活的,哪些对象已经“已经”“死了”(也就是说,它不能再以任何方式使用)
1。引用计数算法
为对象添加引用计数器。当有引用时,计数器值加1,当引用过期时,计数器值减1,任何时候计数器为0的对象都不能再使用。这种方法非常高效,在大多数情况下是一个非常好的算法。
但是JVM中对象之间的循环引用问题将很难解决。如果两个对象互相调用,就会出现类似死锁的情况,即在这个地方互相打电话。等于1,所以它不能告诉GC收集器回收它们。但实际情况是这两个对象后面不再被调用,所以这种方法虽然简单有效,但并不是我们的首选。 虚拟机不使用该算法来判断对象是否存活。
2。可达性分析算法
使用了多个GC Roots对象(包括:虚拟机栈中引用的对象、方法作用域中类静态属性引用的对象、方法作用域中常量引用的对象、方法作用域中JNI引用的对象)本地方法栈)作为起点,从节点向下搜索,未附加到GCRoots的对象可以被重用。下图中的对象4和5被认为是可回收对象。
后来Java扩展了引用的概念,即对象不仅有引用和无引用两种概念,而是扩展到了4种:
强引用:看起来像“Object obj=new Object()”一样只要由于强引用的存在,垃圾收集器将永远不会回收引用的对象。
软引用:用于描述一些有用但不是必需的对象。对于软引用对象,在内存溢出异常之前,那些对象会被纳入第二次重用的重用范围中。
弱引用比软引用弱。与弱引用关联的对象只能存活到下一次垃圾回收发生为止。当垃圾回收发生时,无论内存是否足够,都只会回收弱引用的对象。
虚拟引用是最弱的引用关系。对象是否具有虚拟引用对其生存时间没有影响。唯一目的是当该物品被收集器回收时接收系统消息。
一个对象要真正宣告“死亡”,至少需要两次标记过程。当可达性分析时发现对象没有被GC Roots连接时,会对该对象进行第一次标记并进行第一次过滤。过滤条件是判断对象是否应该执行finalize()方法。如果要执行的话,就会将该对象放入F-Queue队列中,对该对象执行finalize()方法。如果finalize()方法允许对象再次链接到GC Roots,那么该对象将再次存活,否则将被第二次标记,等待垃圾收集的到来
2. 垃圾收集算法
目前Java栈中的对象分为新生代和老年代。对于新生代的对象,使用复制算法清理
1。复制算法
将可用内存空间划分为一个较大的Eden空间和两个较小的From Survivor(S0)和To Survivor(S1)空间。每次仅使用伊甸园和 S 区域之一。例如。这次使用的是S0范围。回收时,将Eden区和S0区幸存的物品一次性复制到S1,最后清理Eden区和S0区的物品。 HotSpot 虚拟机的 Eden:S0:S1 之间的默认大小比例为 8:1:1。这是因为新生代中的大部分甚至98%的物品都是“生死存亡”的。如果S区大小不够,就会依靠老年代的内存进行分配保证。
2。如何判断一个对象是否从新生代变为老年代
每次经历一次Minor GC(复制算法回收对象)时,该对象的年龄就会加一。当对象年龄达到15年时,新生代将对象移入老年代。
如果将无法存放到Survivor区的对象放入老年代:首先将对象分配到Eden区,然后通过Minor GC将对象进入Survivor区。当Survivor区无法保存对象时,该对象就会被放入老年代。
新生成的大对象也会直接放到老年代中(可以用-XX:+PretenuerSizeThreshold设置)。超过这个大小的对象一生成就会被放入老年代。
3。 Mark-Clean 和 Mark-Complete 算法
在老年代,由于对象的存活率较高,并且没有多余的空间保证其分配,因此会使用 mark-clean 或 mark-compact 算法进行复用。对象
标签清理算法:首先标记所有要重用的对象,标记完成后,统一回收所有标记的对象
标签清理算法首先标记所有可重用的对象:,让幸存者对象移动到一端,然后直接清除一端边界外的内存
上述标记过程是基于可达性分析算法中的对象标记判定来实现的。
3。内存设置参数
上面介绍了这么多,那么我们如何操作里面的一些参数呢?
-Xms:初始堆大小 JVM 启动时,指定堆空间大小。
-Xmx:最大堆大小。当JVM运行时,如果原始堆空间不足,可以将最大堆大小扩展到。
-Xmn:设置年轻代的大小。完整堆大小 = 年轻代大小 + 老年代大小 + 持久代大小。永久代一般固定大小为64m,因此增加年轻代会减少老年代的大小。 该值对系统性能影响较大。 Sun官方推荐的配置为整个堆的3/8。
-Xss:设置每个线程的Java堆栈大小。未来,每个线程的Java堆栈大小将是1M。以前,每个线程的堆栈大小为 256K。根据程序线程所需的内存大小进行调整。在相同的物理内存下,减小该值可以产生更多的线程。但操作系统对一个进程的线程数量还是有限制的,不能无限生成。
-XX:NewSize=n:设置年轻代的大小
-XX:NewRatio=n:设置年轻代与老年代的比例。例如如果是3,则表示年轻代与老年代的比例为1:3,年轻代为年轻代+老年代整体的1/4。幸存区域之间的关系。请注意,有两个幸存者区域。例如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代的大小
-XX : MaxTenuringThreshold:设置垃圾的最大年龄。如果设置为0,年轻代对象将直接进入老年代,不经过Survivor区。对于老年代数量较多的应用,可以提高效率。如果将该值设置为较大的值,则年轻代对象会在Survivor区被多次复制,这样可以增加对象在年轻代中的生存时间,增加在年轻代中被重用的概率。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。