The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
The heap is created on virtual machine start-up. Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector(GC));
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the “text” segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.
**运行时常量池 (run-time constant pool)**:
是方法区的一部分,用于存放编译期生成的各种字面量”123”,”LGW” 等字符串常量池,和符号引用。运行时常量池具有动态性,并非只有 Class 文件中的内容才能进入运行时常量池,运行期间也能将新的常量放入池中。如 String.intern() 方法。
方法栈(JVM Stack):
线程私有。存储局部变量表、操作栈、动态链接、方法出口,对象指针。
本地方法栈(Native Method Stack):
线程私有。为虚拟机使用到的 Native 方法服务。如 Java 使用 c 或者 c++编写的接口服务时,代码在此区运行。
⚡ final 修饰的常量会在编译期间就放到调用这个变量的方法的类的常量池中,既不会加载也不会初始化,这一点可以通过反编译ClassActiveUse 看的到,两个类不存在任何关系了,甚至可以在编译完成后将 Obj 的 class 文件删掉仍然可以执行,但是后面的另一个 final 常量很明显在编译期间无法确定值,只有在运行期间才能回去到值,所以会加载并初始化类
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) { longt0= 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. longt1= 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; } }
这是 ClassLoader 类加载类的方法,可以看到中间有一段
if(parent!=null){ c = parent.loadClass(name, false);}
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in classfile classloader_study/myClassLoader/MyObject at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) at java.lang.ClassLoader.defineClass(ClassLoader.java:642) at classloader_study.myClassLoader.MyClassLoader.findClass(MyClassLoader.java:64) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at classloader_study.encryption_study.ClassLoaderTest.main(ClassLoaderTest.java:15)
java.lang.SecurityException: Prohibited package name: java.lang at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662) at java.lang.ClassLoader.defineClass(ClassLoader.java:761) at java.lang.ClassLoader.defineClass(ClassLoader.java:642) at classloader_study.break_parent.SimpleClassLoader.findClass(SimpleClassLoader.java:52) at classloader_study.break_parent.SimpleClassLoader.loadClass(SimpleClassLoader.java:77) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at classloader_study.break_parent.SimpleClassLoaderTest.main(SimpleClassLoaderTest.java:7)
java.lang.SecurityException: Prohibited package name: java.lang at sun.misc.Unsafe.defineClass(Native Method) at classloader_study.break_parent.SimpleClassLoader.findClass(SimpleClassLoader.java:57) at classloader_study.break_parent.SimpleClassLoader.loadClass(SimpleClassLoader.java:83) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at classloader_study.break_parent.SimpleClassLoaderTest.main(SimpleClassLoaderTest.java:7) Exception in thread "main" java.lang.NullPointerException at classloader_study.break_parent.SimpleClassLoaderTest.main(SimpleClassLoaderTest.java:8)
Sub is load byclassloader_study.myClassLoader.MyClassLoader@74a14482 Parent is load bysun.misc.Launcher$AppClassLoader@18b4aac2 Exception in thread "main" java.lang.NoClassDefFoundError: classloader_study/myClassLoader/Sub at classloader_study.myClassLoader.Parent.Hello(Parent.java:15) at classloader_study.myClassLoader.Parent.<init>(Parent.java:10) at classloader_study.myClassLoader.Sub.<init>(Sub.java:10) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.lang.Class.newInstance(Class.java:442) at classloader_study.myClassLoader.ClassLoaderTest.main(ClassLoaderTest.java:14) Caused by: java.lang.ClassNotFoundException: classloader_study.myClassLoader.Sub at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 9 more
publicvoidHello() { //父加载器访问子加载器加载的类 System.out.println("Parent can see the " + Sub.class); } }
Sub 类
publicclassSub { publicSub(){ System.out.println("Sub is load by"+this.getClass().getClassLoader()); newParent(); //构造 Sub 类 //访问父加载器加载的类 System.out.println("Sub can see "+Parent.class); } }
和上面一样删掉 Classpath 中 Sub 类的 class 文件没然后运行
findclass is invoke MyClassLoader is loadclassloader_study.myClassLoader.Sub Sub is load byclassloader_study.myClassLoader.MyClassLoader@74a14482 Parent is load bysun.misc.Launcher$AppClassLoader@18b4aac2 Sub can see classclassloader_study.myClassLoader.Parent
Process finished with exit code 0
没有任何问题,由此就可以证明我们上面的结论是正确的。
❓ 面试题:如何让一个类的 static 代码块执行两次
用不同的类加载器去加载这个类,至于为什么应该不用我多说了吧。
类的卸载和 ClassLoader 的卸载
由 Java 虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中始终不会被卸载,Java 虚拟机本身会引用这些类加载器,而这些类加载器则会始终引用他们所加载的类的 Class 对象,因此这些 Class 对象始终是可达的。
⚡JVM 中的 Class 只有满足以下三个条件,才能被 GC 回收,也就是该 Class 被卸载(unload)
privatestaticvoidloadInitialDrivers() { String drivers; try { drivers = AccessController.doPrivileged(newPrivilegedAction<String>() { public String run() { return System.getProperty("jdbc.drivers"); } }); } catch (Exception ex) { drivers = null; } // If the driver is packaged as a Service Provider, load it. // Get all the drivers through the classloader // exposed as a java.sql.Driver.class service. // ServiceLoader.load() replaces the sun.misc.Providers() // 如果驱动正确打包为 jar 就会用 ServiceLoader 去加载它 AccessController.doPrivileged(newPrivilegedAction<Void>() { public Void run() { /****************************************************/ /*ServiceLoad 工具类,注意这个 ServiceLoad 的加载器,默认就是 TCCL*/ ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); /****************************************************/ /*在这里会获取到一个 Drivers 的迭代器,但是其实还没有开始加载类*/ Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated. * It may be the case that the driver class may not be there * i.e. there may be a packaged driver with the service class * as implementation of java.sql.Driver but the actual class * may be missing. In that case a java.util.ServiceConfigurationError * will be thrown at runtime by the VM trying to locate * and load the service. * * Adding a try catch block to catch those runtime errors * if driver not available in classpath but it's * packaged as service and that service is there in classpath. */ try{ while(driversIterator.hasNext()) { //迭代的过程中通过 next 反射加载并初始化这个驱动字节码 //没有接收返回的数据库驱动实例 driversIterator.next(); } } catch(Throwable t) { // Do nothing } returnnull; } });
privatestaticvoidfail(Class<?> service, URL u, int line, String msg) throws ServiceConfigurationError { fail(service, u + ":" + line + ": " + msg); }
// Parse a single line from the given configuration file, adding the name // on the line to the names list. // privateintparseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError { Stringln= r.readLine(); if (ln == null) { return -1; } intci= ln.indexOf('#'); if (ci >= 0) ln = ln.substring(0, ci); ln = ln.trim(); intn= ln.length(); if (n != 0) { if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) fail(service, u, lc, "Illegal configuration-file syntax"); intcp= ln.codePointAt(0); if (!Character.isJavaIdentifierStart(cp)) fail(service, u, lc, "Illegal provider-class name: " + ln); for (inti= Character.charCount(cp); i < n; i += Character.charCount(cp)) { cp = ln.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) fail(service, u, lc, "Illegal provider-class name: " + ln); } if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln); } return lc + 1; }
/** * Lazily loads the available providers of this loader's service. * @return An iterator that lazily loads providers for this loader's * service */ public Iterator<S> iterator() { returnnewIterator<S>() {
if (user != null) { info.put("user", user); } if (password != null) { info.put("password", password); } // Reflection.getCallerClass() 调用者的 Class 对象 return (getConnection(url, info, Reflection.getCallerClass())); }
一般获取连接都是调用的上面这个方法,这个方法最终会调用另一个重载的方法,同时传入一个调用者的 Class 对象
privatestatic Connection getConnection( String url, java.util.Properties info, Class<?> caller)throws SQLException { /* * When callerCl is null, we should check the application's * (which is invoking this class indirectly) * classloader, so that the JDBC driver class outside rt.jar * can be loaded from here. */ //Caller 就是调用者的 CLass 也就是我们的应用代码类 //获取到我们应用类的类加载器 ClassLoadercallerCL= caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { // synchronize loading of the correct classloader. if (callerCL == null) { //如果为空就,获取线程线下文加载器 callerCL = Thread.currentThread().getContextClassLoader(); } }
// Walk through the loaded registeredDrivers attempting to make a connection. // Remember the first exception that gets raised so we can reraise it. SQLExceptionreason=null; //遍历这个 registeredDrivers 里面都是 DriverInfo for(DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. //检查加载驱动的加载器是不是调用者的类加载器 if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); //获取连接 Connectioncon= aDriver.driver.connect(url, info); if (con != null) { // Success! println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } }