Android开发从GC root分析内存泄漏

图片 5

前言

早先写了一篇深刻剖判 ThreadLocal
内部存款和储蓄器泄漏难题是从理论上剖析ThreadLocal的内部存款和储蓄器泄漏难题,这一篇随笔大家来解析一下其实的内部存款和储蓄器泄漏案例。解析难题的进度比结果更主要,理论结合实际才干通透到底深入分析出内部存款和储蓄器泄漏的由来。

Java的类加载器在sun.misc.Launcher中早先化。

大家常说的排放物回笼机制中会提到GC
Roots这些词,也正是Java虚构机中全数援引的根对象。大家都领悟,垃圾回笼器不会回收GC
Roots以至那个被它们直接引用的指标。可是,对于GC
Roots的概念却不是很领会。它们都席卷哪些对象啊?

案例与剖析

    public Launcher() {
        ExtClassLoader localExtClassLoader;
        try {
          localExtClassLoader = ExtClassLoader.getExtClassLoader();
        }
        catch(IOException localIOException1) {
          throw new InternalError("Could not create extension class loader", localIOException1);
        }

        try {
          this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
        }
        catch(IOException localIOException2) {
          throw new InternalError("Could not create application class loader", localIOException2);
        }

        Thread.currentThread().setContextClassLoader(this.loader);

        String str = System.getProperty("java.security.manager");
        if(str != null) {
          SecurityManager localSecurityManager = null;
          if(( "".equals(str) ) || ( "default".equals(str) ))
            localSecurityManager = new SecurityManager();
          else
            try {
              localSecurityManager = (SecurityManager) this.loader.loadClass(str).newInstance();
            }
            catch(IllegalAccessException localIllegalAccessException) {}
            catch(InstantiationException localInstantiationException) {}
            catch(ClassNotFoundException localClassNotFoundException) {}
            catch(ClassCastException localClassCastException) {}
          if(localSecurityManager != null)
            System.setSecurityManager(localSecurityManager);
          else
            throw new InternalError("Could not create SecurityManager: " + str);
        }
    }

经过查看,通晓JVM中GC Roots的大约分类,然后用本人的言语解释一下:

题材背景

在 Tomcat 中,上面包车型客车代码都在 webapp
内,会导致WebappClassLoader泄漏,无法被回笼。

public class MyCounter {
        private int count = 0;

        public void increment() {
                count++;
        }

        public int getCount() {
                return count;
        }
}

public class MyThreadLocal extends ThreadLocal<MyCounter> {
}

public class LeakingServlet extends HttpServlet {
        private static MyThreadLocal myThreadLocal = new MyThreadLocal();

        protected void doGet(HttpServletRequest request,
                        HttpServletResponse response) throws ServletException, IOException {

                MyCounter counter = myThreadLocal.get();
                if (counter == null) {
                        counter = new MyCounter();
                        myThreadLocal.set(counter);
                }

                response.getWriter().println(
                                "The current thread served this servlet " + counter.getCount()
                                                + " times");
                counter.increment();
        }
}

上边的代码中,只要LeakingServlet被调用过一回,且推行它的线程未有休憩,就能够变成WebappClassLoader泄漏。每一次你
reload 一下运用,就能够多一份WebappClassLoader实例,最终以致PermGen OutOfMemoryException

ExtClassLoader通过ExtClassLoader.getExtClassLoader()初始化。
AppClassLoader通过AppClassLoader.getAppClassLoader(ExtClassLoader)初始化。

  • Class 由System Class Loader/Boot Class
    Loader加载的类对象,那几个目的不会被回笼。供给静心的是其余的Class
    Loader实例加载的类对象不必然是GC
    root,除非这几个类对象偏巧是别的格局的GC root;
  • Thread 线程,激活状态的线程;
  • Stack Local
    栈中的对象。每一种线程都会分配多少个栈,栈中的有的变量也许参数都以GC
    root,因为它们的援引随即只怕被用到;
  • JNI Local
    JNI中的局部变量和参数援用的对象;恐怕在JNI中定义的,也只怕在虚构机中定义
  • JNI Global JNI中的全局变量援用的靶子;同上
  • Monitor Used
    用于保障同步的靶子,比如wait(卡塔尔,notify(State of Qatar中应用的目的、锁等。
  • Held by JVM
    JVM持有的靶子。JVM为了非凡用场保留的对象,它与JVM的现实得以完结有关。比如有System
    Class Loader, 一些Exceptions对象,和一些别的的Class
    Loader。对于那个类,JVM也从可是多的音信。

解决难点

近期我们来思量一下:为啥上边的ThreadLocal子类会引致内存泄漏?

    public static ExtClassLoader getExtClassLoader() throws IOException {
      File[] arrayOfFile = getExtDirs();
      try {
        return( (ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction(arrayOfFile) {
          public Launcher.ExtClassLoader run() throws IOException {
            int i = this.val$dirs.length;
            for( int j = 0; j < i; ++j ) {
              MetaIndex.registerDirectory(this.val$dirs[j]);
            }
            return new Launcher.ExtClassLoader(this.val$dirs);
          }
        }) );
      }
      catch(PrivilegedActionException localPrivilegedActionException) {
        throw( (IOException) localPrivilegedActionException.getException() );
      }
    }

此地的参照他事他说加以调查资料有:

WebappClassLoader

率先,大家要搞了解WebappClassLoader是怎样鬼?

对此运维在 Java EE容器中的 Web 应用来讲,类加载器的完结格局与平常的
Java 应用有所区别。分化的 Web 容器的落实格局也可以有所区别。以 Apache
汤姆cat 来讲,各个 Web
应用都有多个相应的类加载器实例。该类加载器也应用代理情势,所例外的是它是第一尝试去加载有个别类,若是找不到再代理给父类加载器。那与经常类加载器的逐条是相反的。那是
Java Servlet 标准中的推荐做法,其目标是驱动 Web
应用自身的类的先行级高于 Web
容器提供的类。这种代理模式的贰个不等是:Java
主题库的类是不在查找范围之内的。那也是为着保障 Java 宗旨库的档案的次序安全。

也便是说WebappClassLoader是 汤姆cat 加载 webapp 的自定义类加载器,每种webapp 的类加载器都是分歧等的,那是为着隔开分裂应用加载的类。

那么WebappClassLoader的特点跟内部存款和储蓄器泄漏有啥关系啊?近期还看不出来,可是它的三个非常重视的特色值得我们注意:各种webapp 都会友善的WebappClassLoader,那跟 Java 主旨的类加载器不雷同。

大家清楚:导致WebappClassLoader泄漏必然是因为它被别的对象强引用了,那么大家能够尝试画出它们的引用关系图。等等!类加载器的功能到底是什么?为啥会被强引用?

getExtDirs找到系统安排System.getProperty(“java.ext.dirs”卡塔尔(قطر‎中的路线下的保有文件,
让类加载器加载这么些文件。

Yourkit

类的生命周期与类加载器

要消除地点的标题,大家得去研商一下类的生命周期和类加载器的涉嫌。

跟大家那个案例相关的非常重要是类的卸载:

在类使用完以往,倘若满意上边包车型客车景况,类就能够被卸载:

  1. 此类全部的实例都早已被回收,也正是 Java 堆中不真实那类的其余实例。
  2. 加载该类的ClassLoader早就被回笼。
  3. 该类对应的java.lang.Class对象未有另内地方被引述,未有在任哪儿方通过反射访谈该类的法子。

假定上述七个标准全体满意,JVM
就能够在方法区垃圾回笼的时候对类举办卸载,类的卸载进度实际上就是在方法区中清空类新闻,Java
类的万事生命周期就一病不起了。

由Java虚构机自带的类加载器所加载的类,在设想机的生命周期中,始终不会被卸载。Java虚构机自带的类加载器富含根类加载器、扩充类加载器和系统类加载器。Java虚构机本人会始终援引那么些类加载器,而那么些类加载器则会平素援用它们所加载的类的Class对象,由此那些Class对象始终是可触及的。

由客户自定义的类加载器加载的类是能够被卸载的。

小心上边那句话,WebappClassLoader假诺败露了,意味着它加载的类都没有办法儿被卸载,那就解释了为何上边的代码会诱致PermGen OutOfMemoryException

关键点看上边这幅图

图片 1

作者们得以窥见:类加载器对象跟它加载的 Class
对象是双向关联的。这代表,Class
对象也许正是强援引WebappClassLoader,引致它泄漏的元凶。

    public ExtClassLoader( File[] paramArrayOfFile ) throws IOException {
      super(getExtURLs(paramArrayOfFile), null, Launcher.factory);
      SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    }

What are the
roots?
打听过GC Roots之后,能够帮助大家一定内部存款和储蓄器泄漏。因为被GC
roots直接也许直接引用的指标都不会被回笼,所以大家要保管大家用的一部分对象远隔这么些危殆的类。上面遵照GC
root的分类深入分析一下三种内部存款和储蓄器泄漏的原由。

引用关系图

明亮类加载器与类的生命周期的涉嫌随后,大家得以起来画引用关系图了。(图中的LeakingServlet.classmyThreadLocal引用画的不足履实地,重纵然想表明myThreadLocal是类变量的意趣)

图片 2

上边,大家依据上边包车型地铁图来解析WebappClassLoader泄漏的原由。

  1. LeakingServlet持有staticMyThreadLocal,导致myThreadLocal的生命周期跟LeakingServlet类的生命周期同样长。意味着myThreadLocal不会被回笼,弱援引形同虚设,所以当前线程不可能透过ThreadLocalMap的严防措施撤消counter的强引用。
  2. 强引用链:thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader,导致WebappClassLoader泄漏。

ExtClassLoader开始化时将parent设置为null。

1. Class


利用运转进程中国和欧洲动态加载的类都是因而dalvik.system.PathClassLoader的实例加载到设想机中的。那个类对象是GC
root的一种,它们带来的静态变量永久不会被垃圾回收。由此,静态变量持有的“过期”对象将会产生内部存款和储蓄器泄漏。下边举多少个例子。

总结

内部存款和储蓄器泄漏是很难开掘的主题材料,往往由于多地点原因促成。ThreadLocal鉴于它与线程绑定的生命周期成为了内部存款和储蓄器泄漏的常客,稍有不慎就造成大祸。

本文只是对三个一定案例的深入解析,若能以此触类旁通,那正是极好的。最终笔者留另四个像样的案例供读者深入分析。

    public static ClassLoader getAppClassLoader( ClassLoader paramClassLoader ) throws IOException {
      String str = System.getProperty("java.class.path");
      File[] arrayOfFile = ( str == null ) ? new File[0] : Launcher.access$200(str);

      return( (ClassLoader) AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader) {
        public Launcher.AppClassLoader run() {
          URL[] arrayOfURL = ( this.val$s == null ) ? new URL[0] : Launcher.access$300(this.val$path);

          return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);
        }
      }) );
    }

    AppClassLoader( URL[] paramArrayOfURL, ClassLoader paramClassLoader ) {
      super(paramArrayOfURL, paramClassLoader, Launcher.factory);
      this.ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
      this.ucp.initLookupCache(this);
    }

单例:

public class AccountMananger {
    private Context mContext;
    private static AccountMananger instance = null;

    public static AccountMananger getInstance(Context context) {
        if (instance == null) {
            synchronized (AccountManager.class) {
                if (instance == null) {
                    instance = new AccountMananger(context);
                }
            }
        }
        return instance;
    }

    private AccountMananger(Context context) {
        mContext = context;
    }
}

地点这段代码就很危殆,因为单例对象具有一个 Context。它大概是二个
Activity 也说不允许是贰个 ServiceActivity
对象包含大气的构造和财富文件,
一旦它被该单例持有,它所具备的能源在动用甘休前都不会被放出。校订的主意很简短:

    private AccountMananger(Context context) {
        if (context != null) {
            mContext = context.getApplicationContext();
        }
    }

传进来的ContextApplicationContext就足以了。ApplicationContext指标在使用整个生命周期中有且仅有三个指标。持有它的援用不会攻陷更加的多财富。

课后题

要是大家有一个定义在 汤姆cat Common Classpath
下的类(举个例子说在 tomcat/lib 目录下)

public class ThreadScopedHolder {
        private final static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();

        public static void saveInHolder(Object o) {
                threadLocal.set(o);
        }

        public static Object getFromHolder() {
                return threadLocal.get();
        }
}

两个在 webapp 的类:

public class MyCounter {
        private int count = 0;

        public void increment() {
                count++;
        }

        public int getCount() {
                return count;
        }
}
public class LeakingServlet extends HttpServlet {

        protected void doGet(HttpServletRequest request,
                        HttpServletResponse response) throws ServletException, IOException {

                MyCounter counter = (MyCounter)ThreadScopedHolder.getFromHolder();
                if (counter == null) {
                        counter = new MyCounter();
                        ThreadScopedHolder.saveInHolder(counter);
                }

                response.getWriter().println(
                                "The current thread served this servlet " + counter.getCount()
                                                + " times");
                counter.increment();
        }
}

getAppClassLoader从System.getProperty(“java.class.path”State of Qatar处获得要加载的公文,并在开始化时将类加载器的parent设置为ExtClassLoader。

注册/反注册

public class AccountMananger extends Observable{
//单例的内容
}

public class MainActivity extends AppCompatActivity implements Observer {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AccountMananger.getInstance(this).addObserver(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    ...

    @Override
    public void update(Observable observable, Object data) {
        //todo Your logic
    }


}

上面包车型大巴代码也会招致内部存款和储蓄器泄漏,因为注册了监听情势却从没反注册。注册过的监听者都会直接的被单例对象具有,他们都不会被GC回笼。修章:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        AccountMananger.getInstance(this).deleteObserver(this);
    }

不无的注册型的用法都要有反注册。编码的时候养成好习于旧贯,像Activity,Fragment等类在生命周期对等的回调方法中,最棒成没错拉长代码。举个例子在onCreate()措施注册监听之后,马上在onDestroy()方法中反注册。

提示

图片 3

类加载进程

非静态内部类/无名类 + 静态变量

public class MainActivity extends AppCompatActivity {
    private static MyHandler handler = new MyHandler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    public class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

        }
    }
}

非静态内部类会持有外界类的引用(所以它才得以向来访谈外界类的分子变量)。上边代码中的静态handler变量直接持有了MainActivity对象。那样就招致了内部存款和储蓄器泄漏。
缓慢解决的办法正是将在那之中类中对外项指标调用改成public方法,然后将Handler改成静态内部类还是外界叁个类。只怕将将它内置弱引用中。

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

2. Thread


该函数逻辑为:

Runnable/AsyncTask

激活状态的线程是不会被GC回收的,所以它有着的靶子也不会被回收。看上边包车型地铁代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AsyncTasks asyncWork = new AsyncTasks(this);
        ExecutorService defaultExecutor = Executors.newCachedThreadPool();
        defaultExecutor.execute(asyncWork);
    }

    public static class AsyncTasks implements Runnable {
        private Context context;

        public AsyncTasks(Context context) {
            this.context = context;
        }

        @Override
        public void run() {
            while (true) ;
            //正常情况下,线程执行时间不会无限,但可能有5分钟,10分钟
        }
    }
}

线程中具备八个Activity对象,在此个线程活跃的时间内这几个Activity对象都不会被释放。由此,其余线程中尽量不要抱有Activity,Service等大指标。假诺急需采纳Context,尽量利用ApplicationContext

    if 该类已经被当前加载器加载过
        return;
    else if 当前类加载器存在parent加载器
        尝试是否parent加载器加载
    else if 不存在parent加载器器
        尝试使用BootstrapClassLoader加载器加载

    if 父加载器无法加载该类
        使用当前加载器加载。

隐形的线程

举个例子说在叁个Activity中落到实处三个原子钟:

public class MainActivity extends AppCompatActivity {
    private TextView tvClock = null;
    Timer clock = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvClock=findViewById(R.id.tv_clock);
        TimerTask clockTask = new TimerTask() {
            @Override
            public void run() {
                tvClock.setText(updateClockText());
            }
        };
        clock = new Timer();
        clock.schedule(clockTask, 0, 1000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

Timer的一对源码如下:

public class Timer {

    private static final class TimerImpl extends Thread {
    ....
        /**
         * This method will be launched on separate thread for each Timer
         * object.
         */
        @Override
        public void run() {
            while (true) {
                TimerTask task;
                ...
            }
        }
    }

每一个Timer类都运作在二个独立的线程中。例子中我们的Timer目的的线程被安装为1000ms触发一回操作,永不完工。须求静心的是近年来的援用关系Timer->TimerTask->Activity。所以当大家的Activity竣事之后,还恐怕会被GC
root直接持有。那些Activity老是被展开都会多三个对象在进度中,并且永世不会被回笼。
消亡办法就是在ActivityonDestroy情势司令员Timer取消掉。

因为AppClassLoader的parent为ExtClassLoader,而为ExtClassLoader的parent为null。依赖下面的类加载进度,整个类的加载进程为

3. JNI Local & JNI Global


那类对象常常发生在参加Jni交互作用的类中。

比如说非常多close(卡塔尔相关的类,InputStream,OutputStream,Cursor,SqliteDatabase等。这个指标不唯有被Java代码中的引用持有,也会被虚构机中的底层代码持有。在将有所它们的援用设置为null此前,要先将她们close()掉。
再有叁个独特的类是Bitmap。在Android系统3.0事情发生在此以前,它的内部存储器一部分在虚构机中,一部分在虚构机外。由此它的一局地内部存款和储蓄器不参加垃圾回笼,要求大家积极调用recycler()技艺回笼。

动态链接库中的内部存款和储蓄器是用C/C++语言申请的,那么些内部存款和储蓄器不受设想机的总统。所以,so库中的数组,类等都有相当的大希望发生内部存款和储蓄器泄漏,使用的时候必需小心。

图片 4

总结:


  1. 接收静态变量的时候要小心,越发要专一Activity/Service等大目的的传值。在单例格局中能用ApplicationContext的都用ApplicationContext,也许把聚合关系改成重视关系,不在单例对象中保有Context引用;
  2. 养成卓越的代码习贯。注册/反注册要成对现身,ActivityService对象中防止采取非静态内部类/佚名内部类,除非非常知晓引用关系;
  3. 接收三十多线程的时候在意线程存活时间。尽量将集结关系改成信任关系,缩小线程对象具有大对象的时间;
  4. 在使用xxxStream,SqlLiteDatabase,Cursor类的时候要专心释放能源。使用Timer,TimerTask的时候要记得撤除职责。Bitmap在利用完结后要记得recycler()

参照他事他说加以考查作品:
Android
内部存款和储蓄器泄漏总计
Android内部存款和储蓄器泄漏解析及调试

类加载进程

那种类加载的方式即为双亲委托形式,轻便一句话描述正是:
类优先让父加载器加载,父加载器不能加载后才会和煦加载。

信托机制的含义
防卫内部存款和储蓄器中现身多份同样的字节码

举个例子说七个类A和类B都要加载System类:
借使不用委托而是本身加载自身的,那么加载器A就能加载一份System字节码,然后加载器B又会加载一份System字节码,那样内部存款和储蓄器中就现身了两份System字节码。
如果应用委托机制,会递归的向父类查找,也便是首荐用Bootstrap尝试加载,假如找不到再向下。这里的System就能够在Bootstrap中找到然后加载,若是那时类B也要加载System,也从Bootstrap开首,此时Bootstrap发掘已经加载过了System那么直接回到内部存款和储蓄器中的System就能够而不须求再行加载,那样内部存款和储蓄器中就仅有一份System的字节码了。

当Java虚构机要加载二个类时,到底派出哪个类加载器去加载呢?

  • 首先当前线程的类加载器去加载线程中的第多少个类(要是为类A)。
    注:当前线程的类加载器能够经过Thread类的getContextClassLoader(State of Qatar取得,也能够透过setContextClassLoader(State of Qatar本身设置类加载器。
  • 万一类A中引用了类B,Java虚构机将利用加载类A的类加载器去加载类B。
  • 还足以向来调用ClassLoader.loadClass(卡塔尔(قطر‎方法来钦点有些类加载器去加载有个别类。

违反双亲委派情势
本来双亲委派情势并非勒迫供给,在多少情形下会打破双亲委派方式。
如汤姆cat加载类进程。

图片 5

汤姆cat类加载类别

commonLoader:类库可被汤姆cat和具有的Web应用程序同盟接纳。
catalinaLoader:类库可被汤姆cat使用,对持有的Web应用程序都不可知。
sharedLoader:类库可被全体的Web应用程序协同利用,但对汤姆cat自个儿不可以预知。
webappclassLoader:各样Web应用程序单独接收,对其余web应用不可以预知。

假如有十一个Web应用程序都以用Spring来开展公司和治本以来,能够把Spring放到Common或Shared目录下让这么些程序分享。Spring要对客户程序的类举办关押,自然要能访问到用户程序的类,而顾客的次第明显是献身/WebApp/WEB-INF目录中的,那么被CommonClassLoader或SharedClassLoader加载的Spring怎样访谈并不在其加载范围内的客商程序呢?

因为Spring的jar包在common或shared目录中,所以Spring的类加载器为commonLoader恐怕sharedLoader。而Spring中的注入的bean是在投身/WebApp/WEB-INF目录中的,依照
设若类A中引用了类B,Java设想机将动用加载类A的类加载器去加载类B,在Spring中援用web应用的类时,须求commonLoader也许sharedLoader加载去加载,而commonLoader或sharedLoader是回天乏术加载到/WebApp/WEB-INF下的类的,那时就必要commonLoader恐怕sharedLoader加载器来调用子加载器webappclassLoader来加载/WebApp/WEB-INF目录下的类,在促成时是使用线程上下文类加载器,能够实现父加载器对子加载器的逆向访谈。

热部署

再度加载
ClassLoader中一诺千金的方法
loadClass
ClassLoader.loadClass(…卡塔尔国是ClassLoader的入口点。当三个类未有指明用什么样加载器加载的时候,JVM暗许使用AppClassLoader加载器加载未有加载过的class,调用的方式的进口正是loadClass(…卡塔尔国。假如二个class被自定义的ClassLoader加载,那么JVM也会调用这一个自定义的ClassLoader.loadClass(…卡塔尔方法来加载class内部引用的部分其余class文件。重载那么些法子,能兑现自定义加载class的主意,放弃双亲委托机制,可是固然不应用双亲委托机制,举个例子java.lang包中的相关类如故不能够自定义贰个同名的类来替代,首要因为JVM深入深入分析、验证class的时候,博览会开连锁判定。

defineClass
系统自带的ClassLoader,私下认可加载程序的是AppClassLoader,ClassLoader加载三个class,最后调用的是defineClass(…卡塔尔(قطر‎方法,那时候就在想是否能够重复调用defineClass(…卡塔尔(قطر‎方法加载同三个类(或然涂校正卡塔尔国,最终开采调用多次的话会有有关错误:

java.lang.LinkageError
attempted duplicate class definition

之所以三个class被一个ClassLoader实例加载过的话,就不能够再被这几个ClassLoader实例再次加载(这里的加载指的是,调用了defileClass(…卡塔尔(قطر‎放方法,重新加载字节码、深入剖判、验证。State of Qatar。而系统默许的AppClassLoader加载器,他们中间会缓存加载过的class,重新加载的话,就平素取缓存。所与对于热加载的话,只好重复创设叁个ClassLoader,然后再去加载已经被加载过的class文件。

class卸载
在Java中class也是能够unload。JVM中class和Meta新闻寄存在PermGen
space区域。要是加载的class文件过多,那么或然招致PermGen
space区域空中溢出。引起:java.lang.OutOfMemoryErrorPermGen space.
对于有个别Class大家只怕只须求采用二回,就不再必要了,也或者大家匡正了class文件,我们必要再行加载
newclass,那么oldclass就不再供给了。那么JVM怎么样才具卸载Class呢。

JVM中的Class独有满意以下八个尺码,技艺被GC回笼,也等于该Class被卸载(unload):

  • 此类全部的实例皆已经被GC。
  • 加载该类的ClassLoader实例已经被GC。
  • 此类的java.lang.Class对象未有在另内地方被引述。

GC的机缘大家是不可控的,那么同样的大家对此Class的卸载也是不可控的。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图