Java面试之JVM(一)

  1. (JVM)请介绍类的加载过程,什么是父类委托机制(双亲委派模型不准确的叫法)?

加载过程有哪些?这些过程有什么作用?各个过程有问题又会怎么样?为什么叫做双亲委派模型?好处是什么?坏处是什么?有什么解决方式吗?

类加载:JVM将编译好的.class文件(字节码文件)以二进制流的方式加载到我们内存中,并且将二进制流中静态的数据结构转换成我们方法区中动态运行数据结构,并且在对堆内存生成一个java.lang.class对象,作为提供给外界访问我们方法区动态运行数据结构的一个入口。

我们分析上面文字,核心动词加载、转换、生成。

首先是加载,.class如何加载?

我们可以看到ClassLoader有很多的继承类,每个研究完是不可能的,我们所关注的点是上层的设计。

 

上层的设计如何剥离出来呢?

先看ClassLoader的创建

private ClassLoader(Void unused, ClassLoader parent) {
        //类中的变量赋值
        this.parent = parent;

      /**  加载ParallelLoaders类的时候会产生一个Set集合,这里有一个非常好的技巧。我们知道WeakHashMap也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
        当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
        当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
        Collections.newSetFromMap这个Set和被包装的Map拥有相同的key顺序(遍历Set调用的还是Map的keySet),相同的并发特性(也就是说如果对ConcurrentHashMap进行包装,得到的Set也将线程安全).
        现在我们的set集合变成了弱引用的集合了。
        private static final Set<Class<? extends ClassLoader>> loaderTypes =
            Collections.newSetFromMap(
                new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
        static {
            synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class); }
        }
        **/
        //看一下这个弱引用的队列是否包含这个类
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            /**ProtectionDomain有一个重要的方法在静态块里面  SharedSecrets.setJavaSecurityAccess(new JavaSecurityAccessImpl());
            主要用于解决权限问题。

         **/
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

父类的ClassLoader

 private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
                try {
                //加载系统的ClassLoader,跳过权限检查AccessController.doPrivileged

               //https://huangyunbin.iteye.com/blog/1942509
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true }
    }

加载系统类:

   class SystemClassLoaderAction
    implements PrivilegedExceptionAction<ClassLoader> {
    private ClassLoader parent;

    SystemClassLoaderAction(ClassLoader parent) {
        this.parent = parent;
    }

    public ClassLoader run() throws Exception {
        String cls = System.getProperty("java.system.class.loader");
        if (cls == null) {
            return parent;
        }
//得到构造方法
        Constructor<?> ctor = Class.forName(cls, true, parent)
            .getDeclaredConstructor(new Class<?>[] { ClassLoader.class });
//传入构造方法参数      
      ClassLoader sys = (ClassLoader) ctor.newInstance(
            new Object[] { parent });
//设置Contextloader,指定名称的运行时权限sm.checkPermission(new RuntimePermission("setContextClassLoader"));
        Thread.currentThread().setContextClassLoader(sys);
        return sys;
    }
}

查找类:

 

//加载Class类
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //先看一下是否已经加载了
            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();
                    //调用自己的findClass来加载类,所以自定义类加载器的时候重写该方法
                    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;
        }
    }

然后是链接的阶段:

 这个是核心的步骤,就是将原始定义的类信息平滑地转入JVM运行的过程中,这里细分为三个步骤:

 验证:这是虚拟机安全的重要保证,JVM需要核验字节信息是符号Java虚拟机规范的,否则会被认为VerifyError,这样就防止了恶意信息或者不合规的信息危害JVM运行,验证阶段有可能触发更多class的加载。

准备:创建类或接口中的静态变量,并初始化静态变量的初始值。这里的初始化和下面的显示初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会执行更进一步的JVM指令。

解析:在这一步会将常量池中的符号引用替换为直接引用。

最后是初始化阶段:这一步真正去执行类的初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。

如何解决jar包冲突问题?在测试环境和生产环境常常因为环境问题导致jar包冲突,总是花费很多时间排查,如何快速定位呢?

maven进行排除也是可以的。下面介绍一种方法:

http://www.herongyang.com/JVM/ClassLoader-Class-Load-Problem-JAR-Hell.html

try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            String resourceName = "net/sf/cglib/proxy/MethodInterceptor.class";
            Enumeration<URL> urls = classLoader.getResources(resourceName);
            while(urls.hasMoreElements()){
                System.out.println(urls.nextElement());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

     2.(JVM)有哪些方法可以在运行时动态生成一个Java类?

      比较笨的方法通过ProcessBuilder来解决这个问题         https://www.cnblogs.com/xjl456852/p/5839745.html

        1.生成字节码然后由类加载器加载,上面第一个问题方法就可以解决,请深入理解思考

        2.能不能有一个直接操作字节码的工具或者是类库呢?

        字节码和类加载如何进行无缝转化的?发生在类加载的什么过程?问题1中的defineClass方法

        如何利用字节码操纵技术,实现基本动态代理逻辑?

        ASM实现 https://blog.csdn.net/qq_18603599/article/details/80747281

        除了动态代理,字节码操作技术还有哪些应用场景?http请求拦截。

         https://blog.csdn.net/catoop/article/details/51034778

 

       

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值