jvm学习(二)

JVM学习(二)

类的加载过程

系统加载Class类型的文件主要分为三步:加载————>连接---------->初始化。连接又可以分为三步:验证—>准备----->解析

1、加载

  • 通过全类名获取定义此类的二进制字节流
  • 将字节流所代表的静态存储结构转换为方法区内的运行时数据结构
  • 在内存中生成一个代表改类的Class对象,作为方法区这些数据的访问入口

加载过程和来连接过程内容是交叉进行的。加载过程尚为结束,连接过程可能已经开始

2、验证

在这里插入图片描述

3、准备

准备阶段就是为类变量(static变量)分配内存并设置类变量的初始值。通常情况下,初始值设置的为数据类型的默认零值。比如我们定义了public static int value=111 ,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会复制)。特殊情况:比如给 value 变量加上了 fianl 关键字public static final int value=111 ,那么准备阶段 value 的值就被复制为 111。

在这里插入图片描述

4、解析

解析过程就是将常量池的符号引用替换成直接引用。也就是得到类或者字段、方法在内存中的指针或者偏移量。

符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。-在程序实际运行时,只有符号引用是不够的,举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方发表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。

5、初始化

类加载的最后一部,是真正执行类中定义的java程序代码,是执行类构造器<client>()方法的过程

对于<client>()方法的调用,这个方法是带锁线程安全,所以在多线程进行类的初始化的话可能会引起死锁,而且这种死锁很难被发现

只有具备了下面5种条件下,才会进行类的初始化:

  • 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
  • 使用 java.lang.reflect 包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化。
  • 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
  • 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
  • 当使用 JDK1.7 的动态动态语言时,如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且这个句柄没有初始化,则需要先触发器初始化。

类加载器

所有的类都是由类加载器加载,加载的作用就是将.class文件加载到内存,生成Class对象

1、内置的类加载器

​ 除了BootstrapClassLoader,其它的类加载器均由java实现且全部继承自java.lang.ClassLoader

  • BootstrapClassLoader(启动类加载器):最顶层的加载类,由C++实现,负责加载%JAVA_HOME%/lib目录下的jar包和类或者被_Xbootclasspath参数指定的路径种中的所有类
  • ExtensionClassLoader(扩展类加载器):主要负责加载目录%JRE_HOME%/lib/ext目录下的jar包和类,或被java。ext。dirs系统变量所指定的路径下的jar包
  • AppClassLoader(应用程序类加载器):面向我们用户的类加载器,负责加载当前应用classpath下的所有jar包和类

2、双亲委托机制

​ 系统中的ClassLoader在协同工作的时候会默认使用双亲委托机制.即在类加载的时候,系统会首先判断这个类是否被加载过。加载过的类会直接返回,否则会尝试加载。加载的时候,会把该请求委托给父类加载器的loadClass()处理,因此所有的请求最终都会传到启动类加载器BootStrapClassLoader中。当父类加载器无法加载类时,才会由自己处理。

首先由最顶层的类加载器BootstrapClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。

当父类加载器为null时,会使用启动类加载器BootstrapClassLoader作为父类加载器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aw7WBipW-1588760716372)(https://camo.githubusercontent.com/4311721b0968c1b9fd63bdc0acf11d7358a52ff6/68747470733a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f323031392d362f636c6173736c6f616465725f5750532545352539422542452545372538392538372e706e67)]

双亲委托机制的代码:

private final ClassLoader parent; 
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) {//父加载器不为空,调用父加载器loadClass()方法处理
                        c = parent.loadClass(name, false);
                    } else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                   //抛出异常说明父类加载器无法完成加载请求
                }
                
                if (c == null) {
                    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;
        }
    }

好处:双亲委托机制保证了java程序的稳定运行,可以避免类的重复加载(相同的类文件被不同的类加载器加载会产生两个不同的类),也保证了java的核心API不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

jvm判定两个class文件是否相同时,不仅要判断类名是否相同,而且还要判断是否由同一个类加载器加载的。只有两者都满足的情况下,jvm才认为这两个class是相同的

例子:

public class ClassLoaderDemo {
    public static void main(String[] args) {
        System.out.println("ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader());
        System.out.println("The Parent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent());
        System.out.println("The GrandParent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent());
    }
}
ClassLodarDemo's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
The Parent of ClassLodarDemo's ClassLoader is sun.misc.Launcher$ExtClassLoader@1b6d3586
The GrandParent of ClassLodarDemo's ClassLoader is null

可以看出,AppClassLoader的父类加载器为ExtClassLoader。ExtClassLoader的父类加载器为null,这并不代表ExtClassLoader没有父类加载器,而是BootstrapClassLoader

3、自定义加载器

自定义加载器必须继承自java.lang.ClassLoader类,并重写其的findClass()方法。为什么重写findClass方法,因为JDK已经在loadClass中帮我们实现了ClassLoader搜索类的算法,当在LoadClass方法中搜索不到类的时候,会调用findClass方法来搜索类,所以我们只需要重写这个方法。不建议重写loadClass的搜索类算法

java中定义的加载器只加载指定目录下的jar和class,我们想加载其它位置的jar时,就需要自定义加载器。如:我要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑。在这样的情况下,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader。

package classloader;
 
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
 
/**
 * 加载网络class的ClassLoader
 */
public class NetworkClassLoader extends ClassLoader {
	
	private String rootUrl;
 
	public NetworkClassLoader(String rootUrl) {
		this.rootUrl = rootUrl;
	}
 
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		Class clazz = null;//this.findLoadedClass(name); // 父类已加载	
		//if (clazz == null) {	//检查该类是否已被加载过
			byte[] classData = getClassData(name);	//根据类的二进制名称,获得该class文件的字节码数组
			if (classData == null) {
				throw new ClassNotFoundException();
			}
			clazz = defineClass(name, classData, 0, classData.length);	//将class的字节码数组转换成Class类的实例
		//} 
		return clazz;
	}
 
	private byte[] getClassData(String name) {
		InputStream is = null;
		try {
			String path = classNameToPath(name);
			URL url = new URL(path);
			byte[] buff = new byte[1024*4];
			int len = -1;
			is = url.openStream();
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			while((len = is.read(buff)) != -1) {
				baos.write(buff,0,len);
			}
			return baos.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (is != null) {
			   try {
			      is.close();
			   } catch(IOException e) {
			      e.printStackTrace();
			   }
			}
		}
		return null;
	}
 
	private String classNameToPath(String name) {
		return rootUrl + "/" + name.replace(".", "/") + ".class";
	}
 
}
package classloader;
 
public class ClassLoaderTest {
 
	public static void main(String[] args) {
		try {
			/*ClassLoader loader = ClassLoaderTest.class.getClassLoader();	//获得ClassLoaderTest这个类的类加载器
			while(loader != null) {
				System.out.println(loader);
				loader = loader.getParent();	//获得父加载器的引用
			}
			System.out.println(loader);*/
			
 
			String rootUrl = "http://localhost:8080/httpweb/classes";
			NetworkClassLoader networkClassLoader = new NetworkClassLoader(rootUrl);
			String classname = "org.classloader.simple.NetClassLoaderTest";
			Class clazz = networkClassLoader.loadClass(classname);
			System.out.println(clazz.getClassLoader());
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}

在这里插入图片描述

Web容器定义的类加载器

以tomcat为例:

import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class ClassLoaderServletTest extends HttpServlet {
 
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
 
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		ClassLoader loader = this.getClass().getClassLoader();
		while(loader != null) {
			out.write(loader.getClass().getName()+"<br/>");
			loader = loader.getParent();
		}
		out.write(String.valueOf(loader));
		out.flush();
		out.close();
	}
	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		this.doGet(request, response);
	}
 
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
	xmlns="http://java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <servlet>
    <servlet-name>ClassLoaderServletTest</servlet-name>
    <servlet-class>ClassLoaderServletTest</servlet-class>
  </servlet>
 
  <servlet-mapping>
    <servlet-name>ClassLoaderServletTest</servlet-name>
    <url-pattern>/servlet/ClassLoaderServletTest</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值