当前位置: 首页 > 科技观察

一个好的Java程序员必须了解GC的那些事

时间:2023-03-17 10:26:45 科技观察

一个好的Java程序员必须了解GC的工作原理,如何优化GC性能,以及如何与GC进行有限的交互,因为有些应用程序对性能要求更高,比如嵌入式系统,实时系统等,只有全面提高内存的管理效率,才能提高整个应用程序的性能。一个优秀的Java程序员必须了解GC的工作原理,如何优化GC的性能,以及如何与GC进行有限的交互,因为有些应用对性能要求很高,比如嵌入式系统,实时系统等,只有全面提升高效的内存管理才能提升整个应用的性能。本文首先简要介绍了GC的工作原理,然后深入讨论了GC的几个关键问题,最后从GC的角度对Java编程提出了一些提高Java程序性能的建议。GCJava的内存管理的基本原理其实就是对对象的管理,包括对象的分配和释放。对于程序员来说,使用new关键字来分配一个对象;在释放一个对象时,只要将该对象的所有引用都赋值为null,使程序无法再访问该对象,我们称该对象为“不可达”。GC会负责回收所有“不可达”对象的内存空间。对于GC来说,当程序员创建一个对象时,GC就开始监控这个对象的地址、大小和使用情况。通常,GC使用有向图来记录和管理堆中的所有对象。这样就确定了哪些对象“可达”,哪些对象“不可达”。当GC确定某些对象“不可达”时,GC负责回收这些内存空间。但是,为了保证GC可以在不同的平台上实现,Java规范并没有严格规定GC的很多行为。例如,对于使用什么类型的回收算法以及何时回收等重要问题没有明确规定。因此,不同JVM的实现者往往有不同的实现算法。这也给Java程序员的发展带来了很多不确定性。本文研究了与GC工作相关的几个问题,试图减少这种不确定性对Java程序的负面影响。增量GC(IncrementalGC)GC通常是由JVM中的一个或一组进程实现的,它本身与用户程序占用相同的堆空间,运行时也会占用CPU。当GC进程运行时,应用程序停止运行。因此,当GC运行时间较长时,用户可以感受到Java程序的停顿。另一方面,如果GC运行时间太短,对象回收率可能太低,意味着还有很多应该回收的对象没有被回收,仍然占用大量内存。因此,在设计GC时,必须在停顿时间和回收率之间做出权衡。一个好的GC实现允许用户定义他们需要的设置。例如,一些内存有限的设备对内存使用非常敏感。希望GC能够准确的回收内存,而不在意拖慢程序运行速度。此外,一些实时网络游戏不能让程序长时间中断。增量GC是通过一定的回收算法将一个长时间的中断分成许多小的中断,通过这种方式来减少GC对用户程序的影响。虽然增量GC在整体性能上可能不如普通GC高效,但它可以减少程序的最长停顿时间。SunJDK提供的HotSpotJVM可以支持增量GC。HotSpotJVM默认的GC方式是不使用增量GC的。为了启动增量GC,我们必须在运行Java程序时加上-Xincgc参数。HotSpotJVM增量GC的实现使用了TrainGC算法。它的基本思想是将堆中的所有对象按照创建和使用情况进行分组(分级),将经常使用和相关的对象放在一个队中,并随着程序运行不断调整分组。当GC运行时,它总是首先回收最旧的(最近很少访问的)对象。如果整个组都是可回收对象,GC会回收整个组。这样每次GC操作只回收一定比例的不可达对象,保证程序的顺利运行。finalize函数详解finalize是一个位于Object类中的方法。此方法的访问修饰符是受保护的。由于所有类都是Object的子类,因此用户类可以轻松访问此方法。由于finalize函数不会自动实现链式调用,我们必须手动实现,所以finalize函数的最后一条语句通常是super.finalize()。这样我们就可以实现finalize自下而上的调用,即先释放自己的资源,再释放父类的资源。根据Java语言规范,JVM在调用finalize函数之前保证对象不可达,但JVM不保证这个函数一定会被调用。此外,规范还保证finalize函数最多运行一次。很多Java初学者会认为这个方法类似于C++中的析构函数,很多对象和资源的释放都放在这个函数中。实际上,这不是一个很好的方法。有以下三个原因。首先,为了能够支持finalize功能,GC不得不对覆盖这个功能的对象做很多额外的工作。第二,finalize操作完成后,对象可能变为可达,GC会再次检查对象是否可达。所以使用finalize会降低GC的性能。第三,既然GC调用finalize的时间是不确定的,那么这样释放资源也是不确定的。通常,finalize用于释放一些不易控制又非常重要的资源,比如一些I/O操作、数据连接等。这些资源的释放对于整个应用来说是非常关键的。在这种情况下,程序员应该主要通过程序本身来管理(包括释放)这些资源,并以finalize函数释放资源为辅,形成双保险管理机制,而不是仅仅依靠finalize来释放资源。下面举个例子说明finalize函数调用后,可能仍然可达,同时也说明一个对象的finalize可能只运行一次。1classMyObject{23Testmain;//记录Test对象,用于finalize时恢复可达性45publicMyObject(Testt)67{89main=t;//保存Test对象1011}1213protectedvoidfinalize()1415{1617main.ref=this;//恢复thisobject以便这个对象可以到达1819System.out.println("Thisisfinalize");//为了测试finalize只运行一次2021}2223}2425classTest{2627MyObjectref;2829publicstaticvoidmain(String[]args){3031Testtest=newTest();3233test.ref=newMyObject(test);3435test.ref=null;//MyObject对象不可达,finalize会被调用3637System.gc();3839if(test.ref!=null)System.out.println("MyObject还活着");4041}4243}4445运行结果:4647Thisisfinalize4849MyObjectisstillalive,因为finalize函数最多调用一次。程序如何与GC交互Java2增强了内存管理功能,增加了java.lang.ref包,定义了三个引用类。这三个引用类是SoftReference、WeakReference和PhantomReference。通过使用这些引用类,程序员可以在一定程度上与GC进行交互,以提高GC的效率。这些引用类的引用强度介于可达对象和不可达对象之间。创建引用对象也非常容易。比如需要创建一个SoftReference对象,先创建一个对象,使用普通引用(reachableobject);然后创建一个SoftReference来引用该对象;最后将普通引用设置为null。这样,这个对象就只有一个SoftReference引用了。同时,我们称这个对象为软引用对象。SoftReference的主要特点是具有很强的引用功能。这种内存只有在内存不够的时候才会回收,所以当内存足够的时候,一般不会回收。另外,这些引用对象也可以保证在Java抛出OutOfMemory异常之前被设置为null。可以用来实现一些常用图片的缓存,实现Cache功能,保证内存的最大使用,不会造成OutOfMemory。下面是1//申请一个图片对象23Imageimage=newImage();//创建一个Image对象45...67//使用image89...1011//使用图片后,设置为软引用类型,并发布强引用;1213SoftReferencesr=newSoftReference(image);1415image=null;1617...1819//2021if(sr!=null)image=sr.get()whenusednexttime;2223else{2425//由于GCMemory低,image已经发布,需要重新加载;2627image=newImage();2829sr=newSoftReference(image);3031}Weak引用对象和Soft引用对象最大的区别在于GC回收的时候需要检查是否是RecycleSoft引用对象,而对于弱引用对象,GC一直回收。弱引用对象更容易更快地被GC回收。虽然GC必须在运行时回收Weak对象,但是这群关系复杂的Weak对象往往需要多次GC才能完成。Map结构中常使用弱引用对象来引用数据量大的对象。一旦对象的强引用为null,GC可以快速回收对象空间。幻引用用处不大,主要用来辅助finalize函数的使用。Phantom对象指的是一些执行了finalize函数的对象,是不可达对象,但是还没有被GC回收。这种对象可以在一些后期回收工作中辅助完成。我们可以通过重写Reference的clear()方法来增强资源回收机制的灵活性。Java编码的一些建议根据GC的工作原理,我们可以采用一些技巧和方法,让GC运行得更高效,满足应用程序的要求。以下是程序设计的一些建议。1、最基本的建议是尽快释放对无用对象的引用。大多数程序员在使用临时变量时,都是让引用变量在退出活动域(作用域)后自动设置为null。我们在使用这种方法的时候,一定要特别注意一些复杂的对象图,比如数组、队列、树、图等,这些对象之间存在相互引用的关系,比较复杂。对于这样的对象,GC回收它们通常是低效的。如果程序允许,尽快将不用的引用对象赋值给null。这样可以加快GC的工作。2、尽量少用finalize函数。finalize函数是Java提供给程序员释放对象或资源的机会。但是会增加GC的工作量,所以尽量少用finalize的方式回收资源。3.如果需要经常使用的图片,可以使用软应用类型。可以在不引起OutOfMemory的情况下,尽量将图片保存在内存中供程序调用。4、注意集合数据类型,包括数组、树、图、链表等数据结构。这些数据结构更容易被GC回收。复杂的。另外,注意一些全局变量和一些静态变量。这些变量往往容易产生悬垂引用,从而导致内存浪费。5、当程序有一定等待时间时,程序员可以手动执行System.gc()通知GC运行,但Java语言规范不保证GC一定会执行。使用增量GC可以缩短Java程序的停顿时间。