JVM虚拟机(四):JVM 垃圾回收机制概念及其算法

垃圾回收概念和其算法

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

垃圾收集算法

引用计数法:就是个比较古老而经典的垃圾收集算法,其核心就是在对象被其他所引用计数器加1,而当引用时效时则减1,但是这种方式有非常严重的问题:无法处理循环引用的情况、还有就是每次进行加减操作比较浪费系统性能。

标记清除法:分为标记和清除两个阶段进行处理内存中的对象,当然这种方式也有非常大的弊端,就是空间碎片问题,垃圾回收后的空间不是连续的,不连续的内存空间的工作效率要低于连续的内存空间。

复制算法:其核心思想就是将内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中去,之后去清除之前正在使用的内存快中的所有的对象,反复去交换两个内存的角色,完成垃圾收集。

(java中的新生代的from和to空间使用的就是这个算法)

标记压缩法:标记压缩法在标记清除基础之上做了优化,把存活的对象压缩到内存一端,而后进行垃圾清理。(java中老年代使用的就是标记压缩法)

图解新生代使用的复制算法:

文字说明:

新生代中没有GC过的对象在eden区,然后进行GC一次,进入到s0区。然后再次进行GC得时候,就回去S0区去查看这个对象有没有在使用,如果在使用那就把这个对象复制到s1区,然后清除s0区不再使用的对象。再次GC的时候就去S1区,再看看这个对象有没有在使用,如果还在使用,那就复制到S0区。然后清除S1区不在使用的对象。

图解老年代算法:

文字说明:

进行GC时看看老年代有没有在使用的对象,如果有那么就压缩出一个区域把那些实用的对象放到压缩的区域中,然后把不再使用的对象全部回收掉。

为什么老年代使用标记压缩法:因为是老年代的对象相对比较稳定了,使用的次数较多。假设老年代中的对象有一百个,这时老年代进行一次垃圾回收可能就会收集几个对象。

文字说明:

进行GC时看看老年代有没有在使用的对象,如果有那么就压缩出一个区域把那些实用的对象放到压缩的区域中,然后把不再使用的对象全部回收掉。

分代算法:就是根据对象的特点把内存分成N块,而后根据每个内存的特点使用不同的算法。

对于新生代和老年代来说,新生代回收频率很高,但是每次回收耗时都很短,而老年待回收频率较低,但是耗时会相对较长,所以应该尽量减少老年代的GC

垃圾回收时的停顿现象

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

对象如何进入老年代

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

-XX:MaxTenuringThreshold,默认情况下为15.

package com.base001;

import java.util.HashMap;
import java.util.Map;

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];
        }

        //测试进入老年代的对象
        //
        //参数:-Xmx1024M -Xms1024M -XX:+UseSerialGC -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails
        //-XX:+PrintHeapAtGC
//        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);
//        }
//
//        for(int k = 0; k<20; k++) {
//            for(int j = 0; j<300; j++){
//                byte[] b = new byte[1024*1024];
//            }
//        }

    }
}

总结:根据设置的MaxTenuringThreshold参数,可以指定新生代对象经过多少次回收后进入老年代。

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

-XX:PretenureSizeThreshold

package com.base001;

import java.util.HashMap;
import java.util.Map;

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];
        }*/

        //测试进入老年代的对象
        //
        //参数:-Xmx1024M -Xms1024M -XX:+UseSerialGC -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails
        //-XX:+PrintHeapAtGC
        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);
        }

        for(int k = 0; k<20; k++) {
            for(int j = 0; j<300; j++){
                byte[] b = new byte[1024*1024];
            }
        }

    }
}

运行后的控制台:

[GC[DefNew: 279476K->5567K(314560K), 0.0093556 secs] 279476K->5567K(1013632K), 0.0242877 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
[GC[DefNew: 285156K->5567K(314560K), 0.0033584 secs] 285156K->5567K(1013632K), 0.0033896 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 284949K->5567K(314560K), 0.0020226 secs] 284949K->5567K(1013632K), 0.0020704 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 284306K->5567K(314560K), 0.0022780 secs] 284306K->5567K(1013632K), 0.0023128 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 284908K->5567K(314560K), 0.0027272 secs] 284908K->5567K(1013632K), 0.0027671 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 284630K->5567K(314560K), 0.0022646 secs] 284630K->5567K(1013632K), 0.0023033 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 284448K->5567K(314560K), 0.0020810 secs] 284448K->5567K(1013632K), 0.0021280 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 284328K->5567K(314560K), 0.0028784 secs] 284328K->5567K(1013632K), 0.0029179 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 284250K->5567K(314560K), 0.0027738 secs] 284250K->5567K(1013632K), 0.0028109 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 284198K->5567K(314560K), 0.0027430 secs] 284198K->5567K(1013632K), 0.0027896 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC[DefNew: 284164K->5567K(314560K), 0.0027639 secs] 284164K->5567K(1013632K), 0.0028058 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 285166K->5567K(314560K), 0.0027197 secs] 285166K->5567K(1013632K), 0.0027675 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 285151K->5567K(314560K), 0.0031070 secs] 285151K->5567K(1013632K), 0.0031417 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 285141K->5567K(314560K), 0.0021324 secs] 285141K->5567K(1013632K), 0.0021730 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 285135K->5567K(314560K), 0.0023313 secs] 285135K->5567K(1013632K), 0.0023660 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 285131K->0K(314560K), 0.0041250 secs] 285131K->5567K(1013632K), 0.0041664 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC[DefNew: 279561K->0K(314560K), 0.0008313 secs] 285128K->5567K(1013632K), 0.0008688 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 279563K->0K(314560K), 0.0001259 secs] 285130K->5567K(1013632K), 0.0001630 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 279560K->0K(314560K), 0.0001164 secs] 285127K->5567K(1013632K), 0.0001543 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 279562K->0K(314560K), 0.0001204 secs] 285129K->5567K(1013632K), 0.0001686 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 279562K->0K(314560K), 0.0001089 secs] 285129K->5567K(1013632K), 0.0001926 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC[DefNew: 279562K->0K(314560K), 0.0001157 secs] 285129K->5567K(1013632K), 0.0001500 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 def new generation   total 314560K, used 30170K [0x0f200000, 0x24750000, 0x24750000)
  eden space 279616K,  10% used [0x0f200000, 0x10f76b30, 0x20310000)
  from space 34944K,   0% used [0x20310000, 0x20310000, 0x22530000)
  to   space 34944K,   0% used [0x22530000, 0x22530000, 0x24750000)
 tenured generation   total 699072K, used 5567K [0x24750000, 0x4f200000, 0x4f200000)
   the space 699072K,   0% used [0x24750000, 0x24cbfc18, 0x24cbfe00, 0x4f200000)
 compacting perm gen  total 12288K, used 1656K [0x4f200000, 0x4fe00000, 0x53200000)
   the space 12288K,  13% used [0x4f200000, 0x4f39e140, 0x4f39e200, 0x4fe00000)
No shared spaces configured.

总结:使用ProtenureSizeThreshold可以进行指定进入老年代的对象大小,但是要注意TLAB区域优先分配空间

TLAB

TLAB(Thread Local Allocation Buffer)即线程本地分配缓存,从名字上看是一个线程专用的内存分配区域,是为了加速对象分配而生的。每一个线程都会产生一个TLAB,该县城独享的工作区域,java虚拟机使用这种TLAB区来避免多线程冲突问题,提高了对象分配的效率。TLAB空间一般不会太大,当大对象无法在TLAB分配时,则会直接分配到堆上。

-XX:UserTLAB 使用TLAB

-XX:+TLABSize 设置TLAB大小

-XX:TLABRefillWasteFraction 设置维护进入TLAB空间的单个对象大小,它是一个比例值,默认为64,即如果对象大于整个空间的1/64,则在堆创建对象。

-XX:+PrintTLAB 查看TLAB信息

-XX:ResizeTLAB自调整TLABRefillWasteFraction阈值。

package com.base001;

public class Test07 {

    public static void alloc(){
        byte[] b = new byte[2];
    }
    public static void main(String[] args) {

        //TLAB分配
        //参数:-XX:+UseTLAB -XX:+PrintTLAB -XX:+PrintGC -XX:TLABSize=102400 -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=100 -XX:-DoEscapeAnalysis -server
        for(int i=0; i<10000000;i++){
            alloc();
        }

    }
}

运行控制台:

TLAB: gc thread: 0x001bbc00 [id: 7808] desired_size: 100KB slow allocs: 4  refill waste: 1024B alloc: 0.29593     5000KB refills: 168 waste  0.0% gc: 0B slow: 2864B fast: 0B
TLAB totals: thrds: 1  refills: 168 max: 168 slow allocs: 4 max 4 waste:  0.0% gc: 0B max: 0B slow: 2864B max: 2864B fast: 0B max: 0B
[GC 16896K->472K(62976K), 0.0012987 secs]
TLAB: gc thread: 0x08697400 [id: 9448] desired_size: 100KB slow allocs: 0  refill waste: 1024B alloc: 0.29593     5000KB refills: 1 waste 100.0% gc: 102368B slow: 0B fast: 0B
TLAB: gc thread: 0x08696400 [id: 8788] desired_size: 100KB slow allocs: 0  refill waste: 1024B alloc: 0.29593     5000KB refills: 1 waste 100.0% gc: 102400B slow: 0B fast: 0B
TLAB: gc thread: 0x001bbc00 [id: 7808] desired_size: 100KB slow allocs: 0  refill waste: 1024B alloc: 0.64512    10900KB refills: 167 waste  0.0% gc: 0B slow: 2672B fast: 0B
TLAB totals: thrds: 3  refills: 169 max: 167 slow allocs: 0 max 0 waste:  1.2% gc: 204768B max: 102400B slow: 2672B max: 2672B fast: 0B max: 0B
[GC 17368K->472K(62976K), 0.0008755 secs]
TLAB: gc thread: 0x001bbc00 [id: 7808] desired_size: 100KB slow allocs: 0  refill waste: 1024B alloc: 0.76527    12930KB refills: 169 waste  0.0% gc: 0B slow: 2704B fast: 0B
TLAB totals: thrds: 1  refills: 169 max: 169 slow allocs: 0 max 0 waste:  0.0% gc: 0B max: 0B slow: 2704B max: 2704B fast: 0B max: 0B
[GC 17368K->456K(62976K), 0.0007058 secs]
TLAB: gc thread: 0x001bbc00 [id: 7808] desired_size: 100KB slow allocs: 0  refill waste: 1024B alloc: 0.84751    14320KB refills: 169 waste  0.0% gc: 0B slow: 2704B fast: 0B
TLAB totals: thrds: 1  refills: 169 max: 169 slow allocs: 0 max 0 waste:  0.0% gc: 0B max: 0B slow: 2704B max: 2704B fast: 0B max: 0B
[GC 17352K->456K(79872K), 0.0008104 secs]
TLAB: gc thread: 0x001bbc00 [id: 7808] desired_size: 100KB slow allocs: 0  refill waste: 1024B alloc: 0.90096    30445KB refills: 338 waste  0.0% gc: 0B slow: 5408B fast: 0B
TLAB totals: thrds: 1  refills: 338 max: 338 slow allocs: 0 max 0 waste:  0.0% gc: 0B max: 0B slow: 5408B max: 5408B fast: 0B max: 0B
[GC 34248K->464K(79872K), 0.0009320 secs]
TLAB: gc thread: 0x001bbc00 [id: 7808] desired_size: 100KB slow allocs: 0  refill waste: 1024B alloc: 0.93571    31619KB refills: 338 waste  0.0% gc: 0B slow: 5408B fast: 0B
TLAB totals: thrds: 1  refills: 338 max: 338 slow allocs: 0 max 0 waste:  0.0% gc: 0B max: 0B slow: 5408B max: 5408B fast: 0B max: 0B
[GC 34256K->456K(111616K), 0.0008258 secs]

对象创建流程图

原文地址:https://www.cnblogs.com/shamo89/p/9813052.html

时间: 2024-08-01 15:48:48

JVM虚拟机(四):JVM 垃圾回收机制概念及其算法的相关文章

详解JVM内存管理与垃圾回收机制 (上)

Java应用程序是运行在JVM上的,得益于JVM的内存管理和垃圾收集机制,开发人员的效率得到了显著提升,也不容易出现内存溢出和泄漏问题.但正是因为开发人员把内存的控制权交给了JVM,一旦出现内存方面的问题,如果不了解JVM的工作原理,将很难排查错误.本文将从理论角度介绍虚拟机的内存管理和垃圾回收机制,算是入门级的文章,希望对大家的日常开发有所助益. 一.内存管理 也许大家都有过这样的经历,在启动时通过-Xmx或者-XX:MaxPermSize这样的参数来显式的设置应用的堆(Heap)和永久代(P

JVM内存管理和垃圾回收机制介绍

http://backend.blog.163.com/blog/static/20229412620128233285220/ 内存管理和垃圾回收机制是JVM最核心的两个组成部分,对其内部实现的掌握是Java开发人员开发出高质量的Java系统的必备条件.最近整理了一些关于JVM内存管理和垃圾回收方面的知识,这里梳理一下,分享给大家,希望能够对Java虚拟机有更深入的了解. 1. JVM内存管理 首先,JVM将内存组织为主内存和工作内存两个部分.主内存中主要包括本地方法区和堆.每个线程都有一个工

JVM系列文章(二):垃圾回收机制

作为一个程序员,仅仅知道怎么用是远远不够的.起码,你需要知道为什么可以这么用,即我们所谓底层的东西. 那到底什么是底层呢?我觉得这不能一概而论.以我现在的知识水平而言:对于Web开发者,TCP/IP.HTTP等等协议可能就是底层:对于C.C++程序员,内存.指针等等可能就是底层的东西.那对于Java开发者,你的Java代码运行所在的JVM可能就是你所需要去了解.理解的东西. 我会在接下来的一段时间,和读者您一起去学习JVM,所有内容均参考自<深入理解Java虚拟机:JVM高级特性与最佳实践>(

【java_基础】JVM内存模型和垃圾回收机制

1. JVM内存模型 Java虚拟机在程序执行过程会把jvm的内存分为若干个不同的数据区域来管理,这些区域有自己的用途,以及创建和销毁时间. 先来看一下Java程序具体执行的过程 上图中的运行数据区(Runtime Data Areas)即为JVM内存区域,其结构如下图: 各区域存储的具体信息: 1.1 程序计数器 程序计数器(Program Counter Register),也有称作为PC寄存器.JVM中的程序计数器跟汇编语言中的程序计数器在功能上是相同的,即指示待执行指令的地址.当 CPU

JVM内存模型及垃圾回收机制

JVM内存模型1.栈Java栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程.存储局部变量.引用.方法.返回值等.StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM就会抛出此异常,这种情况一般是死递归造成的.2.堆 Java中堆是由所有的线程共享的一块内存区域,堆用来保存各种JAVA对象,比如数组,线程对象等. 2.1堆的分代JVM堆一般分为三个部分:Young:年轻代Young区被划分为三部分,Eden区和两个大小严格相同的Su

JVM(四)垃圾回收的实现算法和执行细节

全文共 1890 个字,读完大约需要 6 分钟. 上一篇我们讲了垃圾标记的一些实现细节和经典算法,而本文将系统的讲解一下垃圾回收的经典算法,和Hotspot虚拟机执行垃圾回收的一些实现细节,比如安全点和安全区域等. 因为各个平台的虚拟机操作内存的方法各不相同,且牵扯大量的程序实现细节,所以本文不会过多的讨论算法的具体实现,只会介绍几种算法思想及发展过程. 垃圾回收算法 1.标记-清除算法 标记-清除算法是最基础的算法,像它的名字一样算法分为"标记"和"清除"两个阶段

Java虚拟机四:垃圾回收算法与垃圾收集器

在Java运行时的几个数据区域中,程序计数器,虚拟机栈,本地方法栈3个区域随着线程而生,随线程而灭,因此这几个区域的内存分配和回收具有确定性,不需要过多考虑垃圾回收问题,因为方法结束或者线程结束时,内存就回收了.但是方法区和堆区不一样,一个接口或者实现类所需要的内存可能不一样,一个方法的多个分支需要的内存也可能不一样,只有程序运行时才能知道创建哪些对象,这部分内存的分配和回收是动态的. 在进行垃圾回收时候,首先需要判断哪些对象需要回收,这就涉及到回收算法的问题. 一.垃圾回收算法 1.标记-清除

JVM内存概况与垃圾回收机制详解

参考:<Java虚拟机精讲> 一.JVM虚拟机内部的内存分布的概况 其中方法区我在博文java虚拟机类加载过程内存情况底层源码分析及ClassLoader讲解中详细讲解过,可参考那篇文章.它里面主要保存:运行时常量池.字段和方法数据.构造函数.普通方法的字节码等. PC寄存器会存储正在执行的字节码指令地址,线程私有 Java栈也为线程私有,生命周期与线程的生命周期一致 二.内存分配 1.分配步骤 当我们创建一个对象时,会经历如下步骤: 根据上面的描述得到下面的图 所以对象是分配在堆的Eden区

Java 垃圾回收机制概念梳理

本文摘自我们几周后即将出版的Garbage Collection Handbook一书的样章.同时也让你能熟悉下垃圾回收的基础知识——这选自该书的第一章. 乍一看,垃圾回收所做的事情应当恰如其名——查找并清除垃圾.事实上却恰恰相反.垃圾回收会跟踪所有仍在使用的对象,然后将剩余的对象标记为垃圾.牢记了这点之后,我们再来深入地了解下这个被称为“垃圾回收”的自动化内存回收在JVM中到底是如何实现的. 手动管理内存 在介绍现代版的垃圾回收之前,我们先来简单地回顾下需要手动地显式分配及释放内存的那些日子.