Java垃圾回收

Java判断对象是否存活的方法

  • 引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减一;任何时刻计数器为0的对象就是不可能再被使用的。主流java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象间相互循环引用的问题。
    `public class ReferenceCountingGC{
    public object instance = null;

    }
    objA.instance=objB以及
    objB.instance=objA `

  • 可达性分析算法:通过一系列的称为“GC ROOTS”的对象作为起始点,从这些节点开始向下搜索,所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
    在Java语言中,可以作为GC Roots的对象包括下面几种
    1)虚拟机栈(栈帧中的本地变量表)中引用的对象
    2)方法区中类静态属性引用的对象
    3)方法区中常量引用的对象
    4)本地方法栈中JNI(及一般说的native方法)的引用的对象

对象死亡的判定

即使在可达性分析算中不可达的对象,如果覆盖了finalize()方法,那么可以在finalize方法中重新使自身可达,从而避免被回收。
####方法区的回收
主要回收两部分内容:废弃常量和无用的类。
废弃的常量:例如字符串“adc”,当系统中没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个“abc”常量就会被系统清理出常量池。
无用的类:该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例、加载该类的classLoader已经被回收、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。(可以在虚拟机参数中配置是否对类进行回收,在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能)

垃圾收集算法
  • 标记-清除算法:效率问题、空间碎片
  • 复制算法:将内存分为两块,每次只使用其中一块。当这一块内存用完了,就将还活着的对象复制到另一块上面,然后再把一使用过的内存空间一次清理掉。
  • 标记-整理算法:让所有存活的对象向一端移动,然后清理掉端边界以外的内存
  • 分代收集算法:一般把Java堆分为新生代和老年代,在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,所以选择复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行担保分配,就必须使用“标记——清理”或者“标记——整理”算法来进行回收。
HotSpot算法的实现
  • 枚举根节点(stop the world)
  • 安全点(存放OopMap,程序执行时并非在所有地方都能停顿下来开始GC)
  • 安全区域
垃圾收集器

这里写图片描述

垃圾收集器

新生代的收集器似乎都要停止用户线程

  • Serial收集器:新生代、单线程、复制算法、“stop the world”,可以与CMS、Serial Old搭配使用

  • ParNew收集器:新生代、多线程、Serial收集器的多线程版本、复制算法、“stop the world”,可以与CMS、Serial Old搭配使用

  • Parallel Scavenge收集器:新生代、多线程、可控制的吞吐量(运行用户代码时间/(运行用户代码时间+垃圾收集时间))、与Parallel old,Serial Old搭配使用、高吞吐量可以高效率地利用CPU时间,尽快完成程序的运算任务

  • SerialOld收收集器:老年代、标记-整理(可以与Parallel Scavenge、CMS搭配使用,作为补充)

  • Parrallel Old收集器:老年代、多线程、标记-整理

  • CMS收集器:标记-清除,以最短回收停顿时间为目标的收集器,重视服务器响应速度
    1.初始标记
    2.并发标记
    3.重新标记
    4.并发清除
    这里写图片描述
    CMS收集器的缺点
    1)对CPU资源敏感
    2)CMS收集器无法处理浮动垃圾
    3)基于标记-清除算法,会产生碎片

  • G1收集器:G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK 1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点。

  • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。

  • 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。

  • 空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。

  • 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实
    时Java(RTSJ)的垃圾收集器的特征了。

    在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
    G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
    在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。
    如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤:

    • 初始标记(Initial Marking)
    • 并发标记(Concurrent Marking)
    • 最终标记(Final Marking)
    • 筛选回收(Live Data Counting and Evacuation)

初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。而最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,从Sun公司透露出来的信息来看,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。

这里写图片描述

Minor GC与Major GC(full gc)

几种gc类型

针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:

  • Partial GC:并不收集整个GC堆的模式
    • Young GC:只收集young gen的GC
    • Old GC:只收集old gen的GC。只有CMS的concurrent collection是这个模式
    • Mixed GC:收集整个young gen以及部分old gen的GC。只有G1有这个模式
  • Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等所有部分的模式。
    Major GC通常是跟full GC是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的full GC还是old GC。
    最简单的分代式GC策略,按HotSpot VM的serial GC的实现来看,触发条件是:
  • young GC:当young gen中的eden区分配满的时候触发。注意young GC中有部分存活对象会晋升到old gen,所以young GC后old gen的占用量通常会有所升高。
  • full GC:当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC(因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先触发一次单独的young GC);或者,如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。
    HotSpot VM里其它非并发GC的触发条件复杂一些,不过大致的原理与上面说的其实一样。当然也总有例外。Parallel Scavenge(-XX:+UseParallelGC)框架下,默认是在要触发full GC前先执行一次young GC,并且两次GC之间能让应用程序稍微运行一小下,以期降低full GC的暂停时间(因为young GC会尽量清理了young gen的死对象,减少了full GC的工作量)。控制这个行为的VM参数是-XX:+ScavengeBeforeFullGC。这是HotSpot VM里的奇葩嗯。可跳传送门围观:JVM full GC的奇怪现象,求解惑? - RednaxelaFX 的回答并发GC的触发条件就不太一样。以CMS GC为例,它主要是定时去检查old gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对old gen做并发收集。

深入理解Java G1垃圾收集器
G1 GC日志分析

垃圾收集器常用参数设置及选择
参数名称含义
-XX:-UseSerialGC虚拟机运行在Client模式下默认值,打开此开关后,使用Serial+Serial Old收集器组合进行内存回收
-XX:-UseParNewGC打开此开关后,使用ParNew + Serial Old收集器组合进行内存回收
-XX:UseConcMarkSweepGC打开此开关后,使用ParNew+CMS+Serial Old收集器组合进行内存回收。Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用
-XX:UseParallelGC虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收
-XX:UseParallelOldGC打开此开关后,使用Parallel Scavenge + Parallel Old的收集器组合进行内存回收
-XX:SurvivorRatio新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Survivor=8:1
PretenureSizeThreshold直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
MaxTenuringThreshold晋升到老年代的对象年龄。每个对象再坚持过一次Minor GC之后,年龄就加1,当超过这个参数值时就进入老年代
UseAdaptiveSizePolicy动态调整Java堆中各个区域的大小以及进入老年代的年龄
ParallelGCThreads设置并行GC时进行内存回收的线程数
GCTimeRatioGC时间站总时间的比率,默认值为99,及允许1%的GC时间,仅在使用Parallel Scavenge收集器时生效
MaxGCPauseMills设置GC的最大停顿时间。仅在使用Parallel Scavenge收集器时生效
CMSInitiatingOccupanyFraction设置CMS收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%,仅在使用CMS收集器时生效
UseCMSCompactAtFullCollection设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用CMS收集器时生效
CMSFullGCsBeforeCompaction设置CMS收集器在进行若干次垃圾收集后再启动一次内存碎片整理。仅在使用CMS收集器时生效。

新生代gc:Minor GC:指发生在新生代的垃圾收集动作。Minor GC比较频繁,回收速度也比较快
老年代GC:Major GC/Full GC:指发生在老年代的GC。出现了Major GC,一般会伴随着Minor GC。Major GC速度一般比Minor GC慢10被以上。

内存分配与回收策略
  • 对象优先在Eden分配
    private static final int_1MB=1024*1024; public static void testAllocation(){ byte[]allocation1,allocation2,allocation3,allocation4; allocation1=new byte[2*_1MB]; allocation2=new byte[2*_1MB]; allocation3=new byte[2*_1MB]; allocation4=new byte[4*_1MB];//出现一次Minor GC
    gc日志:
[0.125s][info   ][gc,start     ] GC(0) Pause Young (Allocation Failure)
[0.128s][info   ][gc,heap      ] GC(0) DefNew: 6838K->1023K(9216K)
[0.128s][info   ][gc,heap      ] GC(0) Tenured: 0K->4154K(10240K)
[0.128s][info   ][gc,metaspace ] GC(0) Metaspace: 5172K->5172K(1056768K)
[0.128s][info   ][gc           ] GC(0) Pause Young (Allocation Failure) 6M->5M(19M) 3.044ms
[0.128s][info   ][gc,cpu       ] GC(0) User=0.00s Sys=0.00s Real=0.00s
[0.129s][info   ][gc,heap,exit ] Heap
Disconnected from the target VM, address: '127.0.0.1:55239', transport: 'socket'
[0.129s][info   ][gc,heap,exit ]  def new generation   total 9216K, used 7378K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
[0.129s][info   ][gc,heap,exit ]   eden space 8192K,  77% used [0x00000000fec00000, 0x00000000ff234b28, 0x00000000ff400000)
[0.129s][info   ][gc,heap,exit ]   from space 1024K,  99% used [0x00000000ff500000, 0x00000000ff5ffff8, 0x00000000ff600000)
[0.129s][info   ][gc,heap,exit ]   to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
[0.129s][info   ][gc,heap,exit ]  tenured generation   total 10240K, used 4154K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
[0.129s][info   ][gc,heap,exit ]    the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa0e8b0, 0x00000000ffa0ea00, 0x0000000100000000)
[0.129s][info   ][gc,heap,exit ]  Metaspace       used 5179K, capacity 5232K, committed 5376K, reserved 1056768K
[0.129s][info   ][gc,heap,exit ]   class space    used 436K, capacity 464K, committed 512K, reserved 1048576K

解析:执行testAllocation()中分配allocation4对象的语句时会发生一次Minor GC,这次GC的
结果是新生代6651KB变为148KB,而总内存占用量则几乎没有减少(因为allocation1、
allocation2、allocation3三个对象都是存活的,虚拟机几乎没有找到可回收的对象)。这次
GC发生的原因是给allocation4分配内存的时候,发现Eden已经被占用了6MB,剩余空间已不
足以分配allocation4所需的4MB内存,因此发生Minor GC。GC期间虚拟机又发现已有的3个
2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分
配担保机制提前转移到老年代去。
这次GC结束后,4MB的allocation4对象顺利分配在Eden中,因此程序执行完的结果是
Eden占用4MB(被allocation4占用),Survivor空闲,老年代被占用6MB(被allocation1、
allocation2、allocation3占用)。

  • 大对象直接进入老年代
    所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长
    的字符串以及数组(笔者列出的例子中的byte[]数组就是典型的大对象)。大对象对虚拟机
    的内存分配来说就是一个坏消息(替Java虚拟机抱怨一句,比遇到一个大对象更加坏的消息
    就是遇到一群“朝生夕灭”的“短命大对象”,写程序的时候应当避免),经常出现大对象容易
    导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老
    年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(复习
    一下:新生代采用复制算法收集内存)。(当程序中确实需要使用到一些大对象,可以设置-XX:PretenureSizeThreshold参数,让大对象直接进入老年代,避免频繁的GC)
注意

PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效,Parallel Scavenge收集器不
认识这个参数,Parallel Scavenge收集器一般并不需要设置。如果遇到必须使用此参数的场
合,可以考虑ParNew加CMS的收集器组合。

public class Main {

    private static final int _1MB=1024*1024;

public static void testPretenureSizeThreshold(){
        byte[]allocation;
        allocation=new byte[4*_1MB];//直接分配在老年代中
    }


    public static void main(String[] args) {

        Main.testPretenureSizeThreshold();

    }
}

[0.132s][info   ][gc,heap,exit ] Heap
[0.132s][info   ][gc,heap,exit ]  def new generation   total 9216K, used 2906K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
[0.132s][info   ][gc,heap,exit ]   eden space 8192K,  35% used [0x00000000fec00000, 0x00000000feed6b68, 0x00000000ff400000)
[0.132s][info   ][gc,heap,exit ]   from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
[0.132s][info   ][gc,heap,exit ]   to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
[0.132s][info   ][gc,heap,exit ]  tenured generation   total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
[0.132s][info   ][gc,heap,exit ]    the space 10240K,  40% used [0x00000000ff600000, 0x00000000ffa00010, 0x00000000ffa00200, 0x0000000100000000)
[0.132s][info   ][gc,heap,exit ]  Metaspace       used 5190K, capacity 5296K, committed 5376K, reserved 1056768K
[0.132s][info   ][gc,heap,exit ]   class space    used 436K, capacity 464K, committed 512K, reserved 1048576K

  • 长期存活的对象将进入老年代
    既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象
    应放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给每个对象定义了一个对
    象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被
    Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中
    每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就
    将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:
    MaxTenuringThreshold设置。
private static final int _1MB=1024*1024;

    public static void testTenuringThreshold(){
        byte[] allocation1,allocation2,allocation3;

        allocation1=new byte[_1MB/4];
//什么时候进入老年代取决于XX:MaxTenuringThreshold设置

        allocation2=new byte[4*_1MB];

        allocation3=new byte[4*_1MB];

        allocation3=null;

        allocation3=new byte[4*_1MB];
    }



    public static void main(String[] args) {

        Main.testTenuringThreshold();

    }
[0.143s][info   ][gc,heap      ] GC(0) DefNew: 7047K->1024K(9216K)
[0.143s][info   ][gc,heap      ] GC(0) Tenured: 0K->4410K(10240K)
[0.143s][info   ][gc,metaspace ] GC(0) Metaspace: 5182K->5182K(1056768K)
[0.143s][info   ][gc           ] GC(0) Pause Young (Allocation Failure) 6M->5M(19M) 3.186ms
[0.143s][info   ][gc,cpu       ] GC(0) User=0.00s Sys=0.00s Real=0.00s
[0.143s][info   ][gc,start     ] GC(1) Pause Young (Allocation Failure)
[0.145s][info   ][gc,heap      ] GC(1) DefNew: 5120K->0K(9216K)
[0.145s][info   ][gc,heap      ] GC(1) Tenured: 4410K->5434K(10240K)
[0.145s][info   ][gc,metaspace ] GC(1) Metaspace: 5182K->5182K(1056768K)
[0.145s][info   ][gc           ] GC(1) Pause Young (Allocation Failure) 9M->5M(19M) 1.274ms
[0.145s][info   ][gc,cpu       ] GC(1) User=0.00s Sys=0.00s Real=0.00s
[0.145s][info   ][gc,heap,exit ] Heap
[0.145s][info   ][gc,heap,exit ]  def new generation   total 9216K, used 4150K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
[0.145s][info   ][gc,heap,exit ]   eden space 8192K,  50% used [0x00000000fec00000, 0x00000000ff00dbf8, 0x00000000ff400000)
[0.145s][info   ][gc,heap,exit ]   from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
[0.145s][info   ][gc,heap,exit ]   to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
[0.145s][info   ][gc,heap,exit ]  tenured generation   total 10240K, used 5434K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
[0.145s][info   ][gc,heap,exit ]    the space 10240K,  53% used [0x00000000ff600000, 0x00000000ffb4eae8, 0x00000000ffb4ec00, 0x0000000100000000)
[0.145s][info   ][gc,heap,exit ]  Metaspace       used 5189K, capacity 5296K, committed 5376K, reserved 1056768K
[0.145s][info   ][gc,heap,exit ]   class space    used 436K, capacity 464K, committed 512K, reserved 1048576K
  • 动态对象年龄判定
    为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到
    了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总
    和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等
    到MaxTenuringThreshold中要求的年龄。
  • 空间分配担保:在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有
    对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机
    会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代
    最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行
    一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置
    不允许冒险,那这时也要改为进行一次Full GC。
理解GC日志
常见内存泄漏问题与优化方案
  1. 静态集合类引起内存泄漏:
    像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
  1. 当集合里面的对象属性被修改后,再调用remove()方法时不起作用(注意hashCode方法)。

public static void main(String[] args)
{
Set set = new HashSet();
Person p1 = new Person(“唐僧”,“pwd1”,25);
Person p2 = new Person(“孙悟空”,“pwd2”,26);
Person p3 = new Person(“猪八戒”,“pwd3”,27);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println(“总共有:”+set.size()+" 个元素!"); //结果:总共有:3 个元素!
p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变
set.remove(p3); //此时remove不掉,造成内存泄漏
set.add(p3); //重新添加,居然添加成功
System.out.println(“总共有:”+set.size()+" 个元素!"); //结果:总共有:4 个元素!
for (Person person : set)
{
System.out.println(person);
}
}
}
class Person{
int age;
public Person(String s1,String s2,int n){
age=n;
}
public void setAge(int n){
this.age=n;
}
public int hashCode(){
return age;
}
}

3.监听器
4.各种连接
5.内部类和外部模块的引用
6.单例模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值