Unsafe.defineClass挂起-反射速度慢实战案例

作者: HeapDump性能社区 | 来源:发表于2020-02-05 20:09 被阅读0次

    Unsafe.defineClass挂起-反射速度慢

    在Java 8 / SpringBoot 1.5.13 / Tomcat 8.5 Web应用程序中观察到运行时间极长的请求(> 30s)。而且卡顿时间会随程序运行时间变久。但是大部分请求的响应还是正常的。卡顿发生时的cpu负载增长到100%,过几分钟后会自己降下来。

    Threaddumps显示Unsafe.defineClass正在挂起:

    "http-nio-8080-exec-9" #254 daemon prio=5 os_prio=0 tid=0x00007f8a98b4f000 nid=0x2258 runnable [0x00007f89fb0b0000]
       java.lang.Thread.State: RUNNABLE
        at sun.misc.Unsafe.defineClass(Native Method)
        at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:63)
        at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:399)
        at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:394)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:393)
        at sun.reflect.MethodAccessorGenerator.generateMethod(MethodAccessorGenerator.java:75)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:53)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.beans.BeanUtils.copyProperties(BeanUtils.java:618)
        at org.springframework.beans.BeanUtils.copyProperties(BeanUtils.java:538)
        at com.haigui.stateaction.action_impl.DisputeSubmit.insertDisputeInfo(DisputeSubmit.java:128)
    

    卡顿发生时的JVM情况:

    file
    回复

    [你假笨]:从你的这些输出来看,主要是挂载了一个agent来对类进行操作,建议正常环境去掉这个agent,理论上加载的类都加载的差不多了,这个问题就不会有了。

    关于不同虚拟机版本下的-XX:+TraceClassUnloading

    在JDK8中尝试重现类卸载的例子, 加入该参数并无法看到控制台有对应的unload日志输出(macos环境jdk1.8.0_161, linux环境jdk1.8.0_91均有尝试);由于TraceClassUnloading在JDK9后不建议使用了, 被替换为了-Xlog:class+unload=info。 同样的代码在JDK9(macos环境make编译), JDK11下, 在新参数下, 均能看到对应的unload日志。为何?
    测试代码如下:

    
    public class Loop {
    
    
        public static void main(String[] args) throws Exception{
    
            ClassLoaderB loaderB = new ClassLoaderB("CLB");
            loaderB.setPath("/Users/mozilla/core/universe/java-base/target/classes/");
            Class<?> clazz = loaderB.loadClass("com.github.universe.java.base.Pow");
            Object object = clazz.newInstance();
            System.out.println(object);
    
            System.out.println("-----------------");
            loaderB = null;
            clazz = null;
            object = null;
    
            System.gc();
    
            // System.gc();
            while (true){
                Thread.sleep(100000);
            }
        }
    
        private static class ClassLoaderA extends ClassLoader{}
        private static class ClassLoaderB extends ClassLoader {
    
            private String classLoaderName;
    
            //类的扩展名
            private final String fileExtension = ".class";
    
            private String path;
    
            public void setPath(String path) {
                this.path = path;
            }
    
            public ClassLoaderB(String classLoaderName) {
                super();
                this.classLoaderName = classLoaderName;
            }
    
            public ClassLoaderB(String classLoaderName, ClassLoader parent) {
                super(parent);
                this.classLoaderName = classLoaderName;
            }
    
    
            @Override
            protected Class<?> findClass(String className) throws ClassNotFoundException {
                System.out.println("findClass invoked: " + className);
                System.out.println("class loader name: " + this.classLoaderName);
                byte[] data = this.loadClassDate(className);
                return this.defineClass(className, data, 0, data.length);
            }
    
            private byte[] loadClassDate(String name) {
                InputStream is = null;
                byte[] data = null;
                ByteArrayOutputStream baos = null;
    
                try {
                    name = name.replace(".", "/");
                    is = new FileInputStream(new File(this.path + name + this.fileExtension));
                    baos = new ByteArrayOutputStream();
    
                    int ch = 0;
                    while ((ch = is.read()) != -1) {
                        baos.write(ch);
                    }
                    data = baos.toByteArray();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        is.close();
                        baos.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return data;
            }
        }
    }
    

    回复

    [大空翼]:Loop.class这个类的路径也在/Users/mozilla/core/universe/java-base/target/classes/?如果也是在那个位置的话,可能是appclassloader加载的,那就卸载不了的,可以dump出来看看com.github.universe.java.base.Pow这个类的类加载器是哪个。

    [Mozilla]:如果相同等于CL是活的, 确实不能unload。 但是如上所示, 代码了指明让ClassLoaderB去load。而且load成功后我也会打出log在console。而且同一份代码JDK8可以JDK9,11可以就很头疼了。

    [大空翼]:ClassLoader的委托类加载机制,并不一定是你指定的这个类加载器加载的,可能委托给父类加载器加载。

    点击标题查看完整问答

    推荐阅读JVM源码分析之栈溢出完全解读
    推荐阅读 GC 实战—浮动内存导致的 CPU 过高调优

    相关文章

      网友评论

        本文标题:Unsafe.defineClass挂起-反射速度慢实战案例

        本文链接:https://www.haomeiwen.com/subject/ovkexhtx.html