GC
java -Xmx2m-Xms2m Test // 修改jvm中Test类内存大小(待验证)
Java语言对对象的引用有四种类型:
1.强引用类型:直接把引用变量指向对象即为强引用,jvm的垃圾回收机制无法强制回收强引用的java对象。强引用是造成java内存泄漏的主要原因之一。
2.软引用类型:软引用通过SoftReference类来实现,当系统内存空间充足时,软引用的对象不会被系统回收,但当内存空间不足时,就会被系统回收。
注意:person[1].get()默认会运行Person的toString方法。person[1].get().getName();会得到name的值
class Person{ String name; int age; public Person(String name,int age){ this.name = name; this.age = age; } public void setName(String name){ this.name = name; } public String toString(){ return "Person[name="+name+",age="+age+"]"; } } class Test { public static void main(String[] args) { SoftReference<Person>[] person = new SoftReference[100]; for(int i= 0;i<person.length;i++){ person[i] = new SoftReference<Person>(new Person("name"+i,i+10)); } // person[1].get()默认会运行Person的toString方法。 //person[1].get().getName();会得到name的值 System.out.println(person[1].get()); // 通知系统进行垃圾回收 System.gc(); System.runFinalization(); // 垃圾回收机制运行之后,softReference数组里的元素保持不变 System.out.println(person[1].get()); } }
3.弱引用类型:通过WeakReference类来实现。不管系统内存空间是否充足,当系统垃圾回收机制运行时,总会回收该对象所占用的内存。(系统垃圾回收机制的运行时间是不能确定的)注意:不能使用String str = "abc";这样会存到字符串缓存中,GC不会回收缓存的字符串常量
1 class Test { 2 public static void main(String[] args) throws Exception{ 3 // 创建一个字符串对象(不能使用String str = "abc";这样会存到字符串缓存中,GC不会回收缓存的字符串常量) 4 String str = new String("abc"); 5 // 创建一个弱引用,让弱引用指向该对象 6 WeakReference<String> wr = new WeakReference<String>(str); 7 // 将str这个强引用清除 8 str = null; 9 System.out.println(wr.get()); 10 // 人为通知系统进行垃圾回收 11 System.gc(); 12 System.runFinalization(); 13 // 垃圾回收机制运行之后,WeakReference里的元素变为null 14 System.out.println(wr.get()); 15 } 16 }
4.虚引用类型:软引用和弱引用是可以单独使用的,而虚引用不可以。虚引用的主要作用是跟踪对象被垃圾回收的状态,程序可以通过检查与需引用关联的引用队列中是否已经包含指定的需引用,从而了解需引用所引用对象是否即将被回收。PhantomReference类.
jvm垃圾回收机制判断某个对象时候可以回收的唯一标准是:是否还有其他引用指向该对象(即该对象是否处于可达状态)
java对象有三种状态1.可达状态(有引用指向该对象)2.不可达状态(没有引用指向该对象)3.可恢复状态
内存泄露:java的垃圾回收机制主要是回收处于不可达状态的对象。而那些处于可达状态的对象,程序以后永远不会再访问它们,那他们占用的内存空间也不会被回收,这样就产生了内存泄漏。
垃圾回收器的设计算法
1.串行回收(serial)和并行回收(Parallel),serial:不管系统有多少个cpu,始终只用一个cpu来执行垃圾回收操作。Parallel:把整个回收工作拆分成多个部分,每个部分由一个cpu负责。并行回收的效率很高,但复杂度增加,内存碎片也会增加。
2.并发执行(concurrent)和程序停止(stop-the-world),concurrent需要解决和应用程序的执行冲突(应用程序可能会在垃圾回收的过程中修改对象),因此concurrent的系统开销比stop-the-world更高,而且执行也需要更多的内存。
3.压缩(compacting)和不压缩(non-compacting)和复制(coping)
1.标记压缩法(mark-sweep-com-pact):压缩的方式。垃圾回收器从根节点开始访问所有可达对象,将他们标记为可达状态,然后将这些活动对象搬迁到一起,这个过程也叫内存压缩,再接着回收那些不可达对象所占用的内存空间。
2.标记清除(mark-sweep):不压缩的方式。从根节点开始访问所有可达对象,将他们标记为可达状态,然后再遍历一次整个内存区域,将所有没有标记为可达的对象进行回收处理。内存利用率高,但需要两次遍历内存空间,遍历成本较大,因此造成应用程序暂停的时间随堆空间大小线性增大。而且回收的内存往往是不连续的,整理后的内存碎片较多。
3.复制:将对内存分成两个相同空间,从根开始访问每个关联的可达对象,将空间A的可达对象全部复制到空间B,然后一次性回收整个空间。因为不需要理会那些不可达对象,所以遍历成本较小且不会产生内存碎片,但需要巨大的复制成本和较多内存。
对内存的分代回收 : 根据对象生存时间的长短,把堆内存分成三个代:1.young 2.old 3.permanent
分代回收的策略基于两点事实:
1.绝大多数的对象不会被长时间引用,这些对象在young期间就会被回收;
2.很老的对象(生存时间很长)和很新的对象(生存时间很短)之间很少存在相互引用的情况。
基于以上两点事实,对于young代的对象,大部分对象都会很快就进入不可达状态,只有少量对象可以熬到垃圾回收执行,而垃圾回收器只需要保留young代中处于可达状态的对象即可。因此大部分垃圾回收器对young采用复制算法。
old代的大部分对象都是经过数次垃圾回收依然没有被回收掉,因此它们没那么容易gg,而且随着时间的流逝,old代的对象会越来越多,所以old代的空间要比young代的空间大。因此old代的垃圾回收具有以下两个特性:
1.old代垃圾回收的执行频率无需太高,因为很少有对象死掉;
2.每次对old代执行垃圾回收需要更长的时间来完成;
基于以上考虑,old代的垃圾回收器一般使用标记压缩算法。
permanent代主要用于装在class,方法等信息,默认为64M,垃圾回收机制通常不睡回收permanent代中的对象(对于Hibernate,spring这类喜欢aop动态生成类的框架,往往会生成大龄的动态代理类,因此需要更多的permanent代内存)
当young代的内存将要用完的时候垃圾回收机制会对young代进行垃圾回收,垃圾回收机制会采用较高的频率对young进行扫描和回收,因为系统开销小,因此也被称为次要回收。而当old代的内存将要用完时,垃圾回收机制会进行全回收,即对young代和old代都要进行回收,此时回收成本较大,因此也成为主要回收。
内存管理小技巧:
1.尽量使用直接量。如:String str = "aa";
2.使用StringBuilder或StringBuffer来进行字符串拼接。
3.今早释放无用对象的引用。
4.尽量少使用静态变量。
5.避免在循环中创建对象。
6.缓存经常使用的对象。(HashMap)
7.考虑使用softReference。使用软引用时注意软引用的不确定性,当系统内存紧张时,软引用的对象就会被释放,这是获取到的就是null了,所以当取出软引用对象时,应该显示判断对象是否为null,如果为null应重建该对象。
8.尽量不要使用finalize方法。在一个对象失去引用之后,垃圾回收器准备回收该对象之前,会先调用该对象的finalize()方法来执行资源清理。因此,有些开发者可能会考虑使用这个方法来进行资源清理,但实际上垃圾回收机制的工作量以及够大了,尤其是回收young代时大部分会引起应用程序的暂停,垃圾回收器本身已经严重影响性能,此时再用finalize方法则会导致程序运行效率更低。
弱引用demo
public static void main(String[] args) { // 强引用 // MySon mySon = new MySon(1,"zhl","男"); // 弱引用 // WeakReference<MySon> mySonWeakReference = new WeakReference<>(mySon); // 如果弱引用指向的对象还有其它地方引用,则GC之后,该弱引用不会被回收 // mySon = null; // 输出值: zhl // System.out.println("引用类型,GC之前:" + mySonWeakReference.get().getName()); // System.gc(); // System.runFinalization(); // 不设置mySon = null之后输出值:zhl 显示设置mySon = null之后报空指针异常,表示已被回收 // System.out.println("引用类型,GC之后:" + mySonWeakReference.get().getName()); // 验证字符串缓存池对弱引用的影响 // String name = "奥特曼"; // // 弱引用 // WeakReference<String> stringWeakReference = new WeakReference<>(name); // name = null; // // 输出值:奥特曼 // System.out.println("字符串常量类型,GC之前:" + stringWeakReference.get()); // System.gc(); // System.runFinalization(); // // 输出值:奥特曼 为什么此处不管设置不设置name=null,name对象都没有被回收呢。原因是执行String name = "奥特曼"时, // // 会直接在缓存池中创建一个常量“奥特曼”,然后将name指向该常量的地址。GC回收时,不会回收缓存池中的内存空间。 // System.out.println("字符串常量类型,GC之后:" + stringWeakReference.get()); // 验证上述缓存池会对弱引用产生影响的结论 String nameObjct = new String("奥特曼"); // 弱引用 WeakReference<String> stringObjectWeakReference = new WeakReference<>(nameObjct); nameObjct = null; // 输出值:奥特曼 System.out.println("字符串引用类型,GC之前:" + stringObjectWeakReference.get()); System.gc(); System.runFinalization(); // 输出值:null System.out.println("字符串引用类型,GC之后:" + stringObjectWeakReference.get()); }