Java GC不收集“僵尸”对象第二次 [英] Java GC does not gather a "zombie" object for a second time

查看:148
本文介绍了Java GC不收集“僵尸”对象第二次的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图创建一个机制将对象缓存到内存中,以备将来使用,即使这些对象不在上下文中。将会有一个并行的确定性过程,它将通过一个唯一的ID来指示应该再次检索缓存的对象还是应该完全死亡。这是最简单的例子,通过调试信息使事情变得更加简单:

  package com.panayotis.resurrect; 

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

public class ZObject {

private static int IDGEN = 1;

保护int id;
private boolean isKilled = false;

public static final Map< Integer,ZObject> zombies = new HashMap<>();

public static void main(String [] args){
for(int i = 0; i <5; i ++)
System.out.println(* INIT :+ new ZObject()。toString());
gc();
sleep(1000);

if(!zombies.isEmpty())
ZObject.revive(2);

gc();
sleep(1000);

if(!zombies.isEmpty())
ZObject.kill(1);

gc();
sleep(1000);
gc();
sleep(1000);
gc();
sleep(1000);
gc();
sleep(1000);
}

public ZObject(){
this.id = IDGEN ++;
}

protected final finalize()throws Throwable {
String debug =+ zombies.size();
String name = toString();
字符串样式;
if(!isKilled){
style =* Zombie;
zombies.put(id,this);
} else {
style =*** FINAL ***;
zombies.remove(id);
super.finalize();
}
dumpZombies(style ++ debug,name);
}

public String toString(){
return(isKilled?killed:zombies.containsKey(id)?zombie:alive)++ ID;
}

public static ZObject revive(int peer){
ZObject obj = zombies.remove(peer);
if(obj!= null){
System.out.println(* Revive+ obj.toString());
obj.isKilled = false;
} else
System.out.println(* Not found as zombie+ peer);
return obj;


public static void kill(int peer){
int size = zombies.size();
ZObject obj = zombies.get(peer);
String name = obj == null? peer +TERMINATED:obj.toString();
zombies.remove(peer);
dumpZombies(* Kill+ size,name);
if(obj!= null)
obj.isKilled = true;


private static void dumpZombies(String baseMsg,String name){
System.out.println(baseMsg + - >+ zombies.size()+ + name);
for(Integer key:zombies.keySet())
System.out.println(*+ zombies.get(key).toString());
}

public static void gc(){
System.out.println(* Trigger GC);
for(int i = 0; i <50; i ++)
System.gc();


public static void sleep(int howlong){
try {
Thread.sleep(howlong);
} catch(InterruptedException ex){
}
}
}

这段代码将创建5个对象,重新生成第一个对象,然后杀掉第一个对象。我期待在第一次复活后,由于对象没有更多的参考资料,所以要重新输入僵尸通过finalize(它不会)


  • 在再次杀死一个对象后,再次通过finalize方法从内存中移除对象




  • 换句话说,最终确定只被调用一次。我已经检查过,这不是使用此代码的HashMap对象的副产品:

      package com.panayotis.resurrect; 

    import java.util.HashMap;

    public class TestMap {

    private static final HashMap< Integer,TestMap> map = new HashMap<>();

    private static int IDGEN = 1;
    private final int id;

    public static void main(String [] args){
    map.put(1,new TestMap(1));
    map.put(2,new TestMap(2));
    map.put(3,new TestMap(3));
    map.remove(1);
    System.out.println(Size:+ map.size());
    for(int i = 0; i <50; i ++)
    System.gc();
    }

    public TestMap(int id){
    this.id = id;


    protected void finalize()throws Throwable {
    System.out.println(Finalize+ id);
    super.finalize();
    }
    }

    那么,为什么会这样呢?我使用Java 1.8



    编辑既然这不是直接可能的,那么我有什么想法可以做到这一点?

    解决方案

    这正是指定的行为: com / javase / 8 / docs / api / java / lang / Object.html#finalize--rel =nofollow noreferrer> Object.finalize()


    在为一个对象调用 finalize 方法后,在Java虚拟机再次确定没有任何方法之前不会采取进一步的操作通过这个对象可以被任何尚未死亡的线程访问,包括可能被其他对象或类准备完成的可能动作,此时该对象可能会被丢弃。



    对于任何给定对象,Java虚拟机永远不会多次调用 finalize 方法。

    你似乎有一个错误的理解什么是 finalize()方法。这个方法不释放对象的内存,声明一个自定义的非平凡的 finalize()方法实际上是防止对象的内存被释放,因为它必须保存在内存中以便执行该方法,并且之后直到垃圾收集器已经确定它已经无法再次访问。不再调用 finalize()并不意味着对象没有被释放,这意味着它将被释放而不会调用 finalize()



    没有自定义 finalize()方法的类的实例或者具有微不足道的finalize方法(空的或仅由 super.finalize()调用另一个微不足道的终结器)组成,并且都是分配的速度更快,回收速度更快。



    这就是为什么你永远不应该试图为内存实现对象缓存,结果总是比JVM自己的内存管理效率低。但是如果你管理的是一个实际上很昂贵的资源,你可以通过将它分成两种不同的对象来处理它,前端向应用程序提供API,当应用程序不使用它时可能会收集垃圾,以及一个描述实际资源的后端对象,它并不直接被应用程序看到并且可以被重用。

    这意味着资源的价格足够昂贵,这种分离的重量。

      //前端类
    公共类资源{
    final ActualResource actual;

    资源(ActualResource actual){
    this.actual = actual;
    }
    public int getId(){
    return actual.getId();
    }
    public String toString(){
    return actual.toString();
    }
    }
    class ActualResource {
    int id;

    ActualResource(int id){
    this.id = id;
    }

    int getId(){
    return id;
    }

    @Override
    public String toString(){
    returnActualResource [id =+ id +']';






      public class ResourceManager {
    static final ReferenceQueue< Resource> QUEUE = new ReferenceQueue<>();
    static final List< ActualResource> FREE = new ArrayList<>();
    static final Map< WeakReference<?>,ActualResource> USED​​ = new HashMap<>();
    static int NEXT_ID;

    public static synchronized Resource getResource(){
    for(;;){
    参考<?> t = QUEUE.poll();
    if(t == null)break;
    ActualResource r = USED.remove(t);
    if(r!= null)FREE.add(r);
    }
    ActualResource r;
    if(FREE.isEmpty()){
    System.out.println(allocating new resource);
    r = new ActualResource(NEXT_ID ++);
    }
    else {
    System.out.println(reusing resource);
    r = FREE.remove(FREE.size() - 1);
    }
    资源前端=新资源(r);
    USED.put(new WeakReference<>(frontEnd,QUEUE),r);
    返回frontEnd;
    }
    / **
    *允许底层实际资源使用r进行垃圾回收。
    * /
    public static synchronized void stopReusing(Resource r){
    USED.values()。remove(r.actual);
    }
    public static synchronized void clearCache(){
    FREE.clear();
    USED.clear();


    $ / code>

    请注意,manager类可能有任意的控制方法缓存或手动释放资源,上面的方法只是例子。如果您的API支持前端无效,例如在调用 close() dispose()或类似方法之后,可以立即显式释放或重用,而无需等待下一个gc循环。虽然 finalize()只被调用一次,您可以在此控制重用周期的数量,包括排列 zero 次的选项。 p>

    以下是一些测试代码

      static final ResourceManager manager = new ResourceManager( ); 
    public static void main(String [] args){
    Resource r1 = manager.getResource();
    资源r2 = manager.getResource();
    System.out.println(r1 =+ r1 +,r2 =+ r2);
    r1 = null;
    forceGC();

    r1 = manager.getResource();
    System.out.println(r1 =+ r1);
    r1 = null;
    forceGC();

    r1 = manager.getResource();
    System.out.println(r1 =+ r1);

    manager.stopReusing(r1);

    r1 = null;
    forceGC();

    r1 = manager.getResource();
    System.out.println(r1 =+ r1);

    private static void forceGC(){
    for(int i = 0; i <5; i ++)try {
    System.gc();
    Thread.sleep(50);
    } catch(InterruptedException ex){}
    }

    System.gc()仍然不能保证有效)print:

     分配新资源
    分配新资源
    r1 = ActualResource [id = 0],r2 = ActualResource [id = 1]
    重新使用资源
    r1 = ActualResource [id = 0]
    重用资源
    r1 = ActualResource [id = 0]
    分配新资源
    r1 = ActualResource [id = 2]


    I am trying to create a mechanism to cache objects into memory, for future use, even if these objects are out of context. There would be a parallel deterministic process which will dictate (by a unique ID) whether the cached object should be retrieved again or if it should completely die. Here is the simplest example, with debug information to make things easier:

    package com.panayotis.resurrect;
    
    import java.util.Map;
    import java.util.HashMap;
    
    public class ZObject {
    
        private static int IDGEN = 1;
    
        protected int id;
        private boolean isKilled = false;
    
        public static final Map<Integer, ZObject> zombies = new HashMap<>();
    
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++)
                System.out.println("* INIT: " + new ZObject().toString());
            gc();
            sleep(1000);
    
            if (!zombies.isEmpty())
                ZObject.revive(2);
    
            gc();
            sleep(1000);
    
            if (!zombies.isEmpty())
                ZObject.kill(1);
    
            gc();
            sleep(1000);
            gc();
            sleep(1000);
            gc();
            sleep(1000);
            gc();
            sleep(1000);
        }
    
        public ZObject() {
            this.id = IDGEN++;
        }
    
        protected final void finalize() throws Throwable {
            String debug = "" + zombies.size();
            String name = toString();
            String style;
            if (!isKilled) {
                style = "* Zombie";
                zombies.put(id, this);
            } else {
                style = "*** FINAL ***";
                zombies.remove(id);
                super.finalize();
            }
            dumpZombies(style + " " + debug, name);
        }
    
        public String toString() {
            return (isKilled ? "killed" : zombies.containsKey(id) ? "zombie" : "alive ") + " " + id;
        }
    
        public static ZObject revive(int peer) {
            ZObject obj = zombies.remove(peer);
            if (obj != null) {
                System.out.println("* Revive      " + obj.toString());
                obj.isKilled = false;
            } else
                System.out.println("* Not found as zombie " + peer);
            return obj;
        }
    
        public static void kill(int peer) {
            int size = zombies.size();
            ZObject obj = zombies.get(peer);
            String name = obj == null ? peer + " TERMINATED " : obj.toString();
            zombies.remove(peer);
            dumpZombies("*   Kill " + size, name);
            if (obj != null)
                obj.isKilled = true;
        }
    
        private static void dumpZombies(String baseMsg, String name) {
            System.out.println(baseMsg + "->" + zombies.size() + " " + name);
            for (Integer key : zombies.keySet())
                System.out.println("*             " + zombies.get(key).toString());
        }
    
        public static void gc() {
            System.out.println("* Trigger GC");
            for (int i = 0; i < 50; i++)
                System.gc();
        }
    
        public static void sleep(int howlong) {
            try {
                Thread.sleep(howlong);
            } catch (InterruptedException ex) {
            }
        }
    }
    

    This code will create 5 objects, resurrect the first one and then kill the first one. I was expecting

    • After first resurrection, and since the object doesn't have any more references yet, to re-enter zombie state through finalize (which it doesn't)

    • After killing an object again to completely be removed from memory through again the finalize method

    It seems, in other words, that finalize is called only once. I have checked that this is not a byproduct of the HashMap object with this code:

    package com.panayotis.resurrect;
    
    import java.util.HashMap;
    
    public class TestMap {
    
        private static final HashMap<Integer, TestMap> map = new HashMap<>();
    
        private static int IDGEN = 1;
        private final int id;
    
        public static void main(String[] args) {
            map.put(1, new TestMap(1));
            map.put(2, new TestMap(2));
            map.put(3, new TestMap(3));
            map.remove(1);
            System.out.println("Size: " + map.size());
            for (int i = 0; i < 50; i++)
                System.gc();
        }
    
        public TestMap(int id) {
            this.id = id;
        }
    
        protected void finalize() throws Throwable {
            System.out.println("Finalize " + id);
            super.finalize();
        }
    }
    

    So, why this behavior? I am using Java 1.8

    EDIT Since this is not directly possible, any ideas how I can accomplish this?

    解决方案

    This is exactly the specified behavior:

    Object.finalize()

    After the finalize method has been invoked for an object, no further action is taken until the Java virtual machine has again determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, including possible actions by other objects or classes which are ready to be finalized, at which point the object may be discarded.

    The finalize method is never invoked more than once by a Java virtual machine for any given object.

    You seem to have a wrong understanding of what the finalize() method does. This method does not free the object’s memory, declaring a custom non-trivial finalize() method is actually preventing the object’s memory from being freed as it has to be kept in memory for the execution of that method and afterwards, until the garbage collector has determined has it has become unreachable again. Not calling finalize() again does not imply that the object doesn’t get freed, it implies that it will be freed without calling finalize() again.

    Instances of classes without a custom finalize() method or having a "trivial" finalize method (being empty or solely consisting of a super.finalize() call to another trivial finalizer) are not going through the finalization queue at all and are both, allocated faster and reclaimed faster.

    That’s why you should never try to implement an object cache just for the memory, the result will always be less efficient than the JVM’s own memory management. But if you are managing an actually expensive resource, you may handle it by separating it into two different kinds of objects, a front-end providing the API to the application, which may get garbage collected whenever the application doesn’t use it, and a back-end object describing the actual resource, which is not directly seen by the application and may get reused.

    It is implied that the resource is expensive enough to justify the weight of this separation. Otherwise, it’s not really a resource worth caching.

    // front-end class
    public class Resource {
        final ActualResource actual;
    
        Resource(ActualResource actual) {
            this.actual = actual;
        }
        public int getId() {
            return actual.getId();
        }
        public String toString() {
            return actual.toString();
        }
    }
    class ActualResource {
        int id;
    
        ActualResource(int id) {
            this.id = id;
        }
    
        int getId() {
            return id;
        }
    
        @Override
        public String toString() {
            return "ActualResource[id="+id+']';
        }
    }
    

    public class ResourceManager {
        static final ReferenceQueue<Resource> QUEUE = new ReferenceQueue<>();
        static final List<ActualResource> FREE = new ArrayList<>();
        static final Map<WeakReference<?>,ActualResource> USED = new HashMap<>();
        static int NEXT_ID;
    
        public static synchronized Resource getResource() {
            for(;;) {
                Reference<?> t = QUEUE.poll();
                if(t==null) break;
                ActualResource r = USED.remove(t);
                if(r!=null) FREE.add(r);
            }
            ActualResource r;
            if(FREE.isEmpty()) {
                System.out.println("allocating new resource");
                r = new ActualResource(NEXT_ID++);
            }
            else {
                System.out.println("reusing resource");
                r = FREE.remove(FREE.size()-1);
            }
            Resource frontEnd = new Resource(r);
            USED.put(new WeakReference<>(frontEnd, QUEUE), r);
            return frontEnd;
        }
        /**
         * Allow the underlying actual resource to get garbage collected with r.
         */
        public static synchronized void stopReusing(Resource r) {
            USED.values().remove(r.actual);
        }
        public static synchronized void clearCache() {
            FREE.clear();
            USED.clear();
        }
    }
    

    Note that the manager class may have arbitrary methods for controlling the caching or manual release of resources, the methods above are just examples. If your API supports the front-end to become invalid, e.g. after calling close(), dispose() or such alike, immediate explicit freeing or reuse can be provided without having to wait for the next gc cycle. While finalize() is called exactly one time, you can control the number of reuse cycles here, including the option of enqueuing zero times.

    Here is some test code

    static final ResourceManager manager = new ResourceManager();
    public static void main(String[] args) {
        Resource r1 = manager.getResource();
        Resource r2 = manager.getResource();
        System.out.println("r1 = "+r1+", r2 = "+r2);
        r1 = null;
        forceGC();
    
        r1 = manager.getResource();
        System.out.println("r1 = "+r1);
        r1 = null;
        forceGC();
    
        r1 = manager.getResource();
        System.out.println("r1 = "+r1);
    
        manager.stopReusing(r1);
    
        r1 = null;
        forceGC();
    
        r1 = manager.getResource();
        System.out.println("r1 = "+r1);
    }
    private static void forceGC() {
        for(int i = 0; i<5; i++ ) try {
            System.gc();
            Thread.sleep(50);
        } catch(InterruptedException ex){}
    }
    

    Which will likely (System.gc() still isn’t guaranteed to have an effect) print:

    allocating new resource
    allocating new resource
    r1 = ActualResource[id=0], r2 = ActualResource[id=1]
    reusing resource
    r1 = ActualResource[id=0]
    reusing resource
    r1 = ActualResource[id=0]
    allocating new resource
    r1 = ActualResource[id=2]
    

    这篇关于Java GC不收集“僵尸”对象第二次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆