`
suhuanzheng7784877
  • 浏览: 691210 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Ff8d036b-05a9-33b5-828a-2633bb68b7e6
读金庸故事,品程序人生
浏览量:47216
社区版块
存档分类
最新评论

Java基础复习笔记02对象状态、引用种类、垃圾回收形式

阅读更多

1.       有些情况下Java的内存回收是一个比较敏感的问题,就是说在一个运行时服务对内存十分严格、苛刻的服务器环境下,要求运行在虚拟机上的程序几乎占用内存十分的少。当然在垃圾回收这个优先级比较低,而程序员自身有不可控的线程下,程序员几乎不知道自己没用的对象是什么时候回收的,什么时候消亡的。因为Java向全球开发者承诺了“内存的事情能够你不用管,专心写好你的应用业务程序就够了”,但是事实上,咱们真的就不用管内存了吗。很多时候,尤其是笔者在开发Eclipse插件的时候经常看到以前的同事编出来的产品会经常java.lang.OutOfMemoryError: Java heap space。实际上在Myeclipse中也一样,再好的IDE插件也是人写出来的,也会有它的弊病,现在的MyEclipse8.X好臃肿哦。所以作为我们搞Java开发的想开发出一个让用户少抱怨的软件出来,也应该对Java内存管理,垃圾回收机制有一个大致的了解,就算我们不开发Java虚拟机、不开发垃圾回收器算法等等底层的内核,了解这些知识我觉得还是很有好处的,以后写代码就多了一层沉重的东西,跳过这层沉重的东西你就是更高一级的境界了。

2.       有人问,像你说的对资源要求十分苛刻的场景有吗?如果一台服务器连基本的内存都不舍得加,难道这样的公司能运作下去?

是的,在我们大多数人来看,开发的都是应用系统,系统都是为了解决一个领域的问题而开发出来的比如电信综合业务——BOSS、企业资源管理——ERP、客户关系管理系统——CRM等等都是解决实际问题的系统。这些系统的服务器都是企业级的硬件系统,可能大家开发的时候都是用一些开源框架来实现业务代码等等。如果换一个领域,假如让你做中间件产品呢、做系统运行时平台支撑产品呢、做一个数据库代理Proxy中间件呢或者就是基于Eclipse自身开发相应的插件Plugin。就像当下炒得比较火的云计算平台,为了节省资源成本可能分配给你的应用少得可怜,又或者你的应用软件需要和其他的应用软件共享一个服务器资源,这个时候就是要求物理资源比较严格、苛刻的时候,开发的应用程序往往要想运行得比较顺畅,那么还真得在代码质量上下下功夫。

3.       Java的对象在内存中的状态

首先先给出对象得内存状态再给出实例

可达状态:创建了一个对象,有实实在在的变量用到它了,他就是可达的!

可恢复状态:当一个对象不再有任何实实在在的变量用到它了,利用完了(玩儿完踹)!之后垃圾回收器在调用该对象的finalize方法的时候,在方法体内也没有任何变量引用、指向这个对象,还对象就变为了不可达状态!如果还有其他变量的指针指向了他,那么就是又回到了可达状态!其实可恢复状态的走向关键就是看finalize方法的执行体了。

不可恢复状态:不可恢复状态就是相当于对对像下了死刑报告了!!利用完了,该对象已经没有利用价值了,等着让垃圾回收器将该对象“和谐”了吧。

String str = new String("是素还真与谈无欲");
 str变量指向了一个字符串对象,该对象指向了一个对象池中缓存的字符串值是素还真与谈无欲。这个时候该对象是可达状态,因为确实有一个变量str在指向它。

String str = new String("是素还真与谈无欲");
str = new String("不是谈无欲与素还真");

 这个时候是str指向了另一个内存中的对象,该对象指向了对象池缓存中的一个字符串不是谈无欲与素还真。也就是说之前的对象(就是指向是素还真与谈无欲的对象)此时已经没有其他任何实在的变量引用它了,在该对象的finalize执行完毕前,它是一个可恢复状态。如果在后面的程序又有其他变量指向了该对象,那么该对象还是可达状态,如果直到执行完finalize方法后还没有切实的变量引用它,那就证明它真的没有任何利用价值了,垃圾回收器见到这样的对象就说了:“来,该我工作了,老子和谐了你!”。

4.       Java的引用类型

对垃圾回收器来说判断一个对象是否可和谐的标准就在于该对象是否被某个对象引用了。Java引用对象的类型是分为4种情况的。

强引用:这是我们开发人员最常见的引用方式。几乎95%的程序归根结底都是使用new一个对象,这个new就是变量————》强引用对像的方式。强引用的对象就是按照对象状态去回收的,一个强引用的对象在不可恢复状态的时候,谁也救不了你,等着和谐吧。

软引用:对于软引用的对象而言,当内存空间足够时,它不会被回收(当然了如果对象是不可恢复状态的话自然也不能免于被回收的命运)。如果内存空间不够的时候就得回收这些被引用的对象了。垃圾回收器会说:“对不起,现在是困难时期,内存不够,房子不够住的,谁让你爸不是李刚呢(不是强引用)?和谐了啊!”。

package reference;

import java.lang.ref.SoftReference;

class Person {
	String name;
	int age;

	@Override
	public String toString() {
		return name + ":" + age;
	}

}

public class SoftReferenceTest {

	/**
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		SoftReference<Person>[] stringArray = new SoftReference[100000];
		Person person = null;
		for (int i = 0; i < 100000; i++) {
			person = new Person();
			person.name = "叶小钗" + i;
			person.age = i;

			stringArray[i] = new SoftReference<Person>(person);
		}
		System.gc();
		System.runFinalization();
		System.out.println(stringArray[1].get());
	}
}

 上面代码使用了软引用一个对象数组,当内存不够用时stringArray[1].get()的值是null,也就是说软引用释放了该内存区域的值,而stringArray[9999].get()的值不是null,证明软引用声明的连续区域从前往后释放。也就是最先占用内存的最先释放,后占用内存的后释放。

弱引用:如果说强引用的对象是李刚之子,那么弱引用我就比喻为早上卖煎饼的小商贩,而垃圾回收器这个时候我把它比喻为城管。只要城管的一开车过来工作,你看那些小商贩,无论是不是正在给客户摊煎饼的,一个个都拉起了煎饼车,跑哦~~结论很简单,这个空间腾出来了!弱引用比软引用的对象生命周期更短,它更加朝不保夕,一旦垃圾回收器运行工作起来,那么无论这个时候内存空间是否够用,弱引用对象一样被回收掉。就像卖煎饼的商贩,你说他在那个地点做生意碍着精神文明和谐社会建设什么事情了?TMD,城管一来就是不让你用这个地方,有折吗?不说了,看代码吧。

Person person = new Person();
		person.name = "秦假仙";
		person.age = 25;

		WeakReference<Person> personWeakReference = new WeakReference<Person>(
				person);
		System.out.println(personWeakReference.get());
		person = null;
		System.out.println(personWeakReference.get());
		System.gc();
		System.runFinalization();
		System.out.println(personWeakReference.get());

 一旦垃圾回收调用之后,那么弱引用get的是null;

这里要注意,一般弱引用不太会在应用程序中使用到,除非系统需求非常的极端,因为弱引用的对象是随着垃圾回收器的执行而消亡的,而垃圾回收器咱们程序员自身是不知道它什么时候执行的,也就是说弱引用对象生命周期即使是在内存空间非常大的环境下也都是不可控的。一般弱引用还可以使用WeakHashMap来进行弱引用对象的使用。

虚引用:这个笔者也没用过,所以摘录网上的资料写在这里

"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

 

5.       内存溢出

内存溢出经常在哪些场合下会发生呢?实际上主要就是数组、集合、循环中对象的增、减、覆盖操作并没有及时对原来的变量断开指针的引用,导致了该指针引用还客观存在着,尤其咱们一般编程中99.99%都是用的强引用类型,那么垃圾回收器发现这些在内存中的对象还有变量指向它呢,他会认为这块内存区域不应该回收,而实际上在应用程序中此对象已经没什么用处了,而只是我们使用中并没有及时给它断开指针链接罢了。这样内存越来越紧张,到最后连续运行了30天或者更长时间的系统估计也得内存溢出——OutOfMemoryError

6.       垃圾回收的机制

垃圾回收机制是JVM比较核心的东西了,至于原理实际上网上有很多资料在讲解

垃圾收集的目的在于清除不再使用的对象。GC通过确定对象是否被活动对象引用来确定是否收集该对象。GC首先要判断该对象是否是时候可以收集。两种常用的方法是引用计数和对象引用遍历。

引用计数收集器

引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象(不是引用)都有一个引用计数。当一个对象被创建时,且将该对象分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1a = b,b引用的对象+1),但当一个对象的某个引用超过了生命周期或者被设置为一个新值时,对象的引用计数减1。任何引用计数为0的对象可以被当作垃圾收集。当一个对象被垃圾收集时,它引用的任何对象计数减1

优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序不被长时间打断的实时环境比较有利。

缺点 无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

跟踪收集器
早期的JVM使用引用计数,现在大多数JVM采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,GC必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。下一步,GC要删除不可到达的对象。删除时,有些GC只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多GC可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。为此,GC需要停止其他的活动活动。这种方法意味着所有与应用程序相关的工作停止,只有GC运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的 GC不断增加或同时运行以减少或者清除应用程序的中断。有的GC使用单线程完成这项工作,有的则采用多线程以增加效率。

一些常用的垃圾收集器
标记-清除收集器
这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。并且,由于它只是清除了那些未标记的对象,而并没有对标记对象进行压缩,导致会产生大量内存碎片,从而浪费内存。
标记-压缩收集器
有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。

复制收集器
这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,JVM生成的新对象则放在另一半空间中。GC运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。并且对于指定大小堆来说,需要两倍大小的内存,因为任何时候都只使用其中的一半。

增量收集器
增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾,也可理解为把堆栈分成一小块一小块,每次仅对某一个块进行垃圾收集。这会造成较小的应用程序中断时间,使得用户一般不能觉察到垃圾收集器正在工作。

分代收集器
复制收集器的缺点是:每次收集时,所有的标记对象都要被拷贝,从而导致一些生命周期很长的对象被来回拷贝多次,消耗大量的时间。而分代收集器则可解决这个问题,分代收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。JVM生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象(非短命对象)将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。

并行收集器
并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多CPU机器上使用多线程技术可以显著的提高java应用程序的可扩展性。

7.       JVM参数设置

堆设置

-Xms:初始堆大小

-Xmx:最大堆大小

-XX:NewSize=n:设置年轻代大小

-XX:NewRatio=n:设置年轻代和年老代的比值.:3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值.注意Survivor区有两个.:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

-XX:MaxPermSize=n:设置持久代大小

收集器设置

-XX:+UseSerialGC:设置串行收集器

-XX:+UseParallelGC:设置并行收集器

-XX:+UseParalledlOldGC:设置并行年老代收集器

-XX:+UseConcMarkSweepGC:设置并发收集器

垃圾回收统计信息

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

并行收集器设置

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU.并行收集线程数.

-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比.公式为1/(1+n)

并发收集器设置

-XX:+CMSIncrementalMode:设置为增量模式.适用于单CPU情况.

-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU.并行收集线程数.

 

分享到:
评论
1 楼 kp034 2012-02-13  
文笔生动,思路清晰
好文

相关推荐

Global site tag (gtag.js) - Google Analytics