java虚拟机类加载过程内存情况底层源码分析及ClassLoader讲解
2016-01-13 15:15
816 查看
读书笔记加自我总结-----------------------------------------------
《疯狂JAVAj讲义》
《深入理解JAVA虚拟机》第七章虚拟机加载机制
《传智播客Java底层公开课视频》教学视频
参考:
一、虚拟机的类加载机制
加载 连接 初始化 ------------> 程序运行期完成
坏处: 好处:
性能开销 灵活性大大增强
二、虚拟机加载的过程如下
三、加载之后在内存中的样子
假设现在有这样一个类
[java] view
plaincopy
public class Demo {
public static void main(String[] args) {
new Person();
}
}
class Person{
private int age;
public Person()
{
System.out.println("this is Person Construct Method");
}
}
class Animal{
public static String name;
}
它在经过加载之后大致在内存中就如下:(非官方图,个人意淫)
至于new一个类之后在堆中内存怎么存放,咱们下节再细说
四、过程5初始化
自动收集1.类变量赋值,2静态语句块 形成<clinit>方法,各语句的顺序按照在类文件中出现的顺序
虚拟机会保证一个类的<clinit>()方法在多线程环境被正确的加锁、同步。
如果多线程同时初始化一个类,只会一个线程执行<clinit>()方法,其他线程阻塞等待。执行的线程执行结束后,其它线程唤醒之后不会再进入<clinit>()。同一个类加载器下,一个类只会初始化一次。
五、过程1加载:加载器
过程1由一个特定的组件进行完成:类加载器
除了加载阶段(图1的阶段1)用户应用程序可以通过自定义加载器参与,剩下的动作完全由虚拟机主导控制
也就是说类加载器的工作就是“通过一个类的全类名来获取描述此类的二进制字节流”
有三类加载器:参见我的上一篇博客:
ClassLoader是一个抽象类
[java] view
plaincopy
public abstract class ClassLoader
JVM中除根加载器之外的所有加载器都是ClassLoader子类的实例
个人认为它大致有两类方法
1.静态的工具方法
比如
可以通过ClassLoader.getSystemClassLoader();获得AppClass Loader(系统类加载器)
[java] view
plaincopy
@CallerSensitive
public static ClassLoader getSystemClassLoader()
getParent()获取该加载器的父类加载器
[java] view
plaincopy
@CallerSensitive
public final ClassLoader getParent()
2 用于加载类的方法
我们通常通过继承这个类来实现我们的自定义类加载器
1)public的loadClass方法,对外提供的入口,进行加载类
[java] view
plaincopy
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
是ClassLoader类的入口点
为什么叫入口点:
比如我们自己写的OurClassLoader类
OurClassLoader ourCL=new OurClassLoader ()
Class<?> clazz=ourCL.loadClass("OurClass");
从而加载类并得到它的Class对象
2)剩下的基本上都是protected方法
通过覆写这些protected方法定制我们的功能
loadClass(String name,boolean resolve),注意这个是protected方法,不是外部直接使用的(不是你想调用就调用的),我们可以覆写这个方法
[java] view
plaincopy
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;
}
}
它会执行如下步骤
用findLoadedClass(String)来检查是否已经加载类,如果已经加载则直接返回
在父类加载器上调用loadClass()方法。如果父类加载器为null,则使用根类加载器来加载
如果父类加载失败,则调用findClass(String)方法查找类
findClass(String name)
[java] view
plaincopy
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
我们要覆写这个方法来自定义自己的加载类的行为
在这个方法中我们应该调用defineClass方法来把字节码转为Class对象
defineClass()
方法负责将字节码分析成运行时的数据结构,并检验有效性。 此方法是final型,我们也无法重写。
六、附加:父类委托机制
通过上面的过程我们能看到loadClass的确采用了父类委托和缓冲机制
双亲委派模型见我上一篇文章
那么问题来了:如果我们覆写loadClass方法呢,成功避开父类委托和缓冲机制(为什么这么任性),那能加载一个系统中已经存在的类吗?
可以,因为:
JVM是如何判定两个 Java 类是相同的: A.类的全名 B.加载此类的类加载器
我们自定义的一个新的类加载器,则加载这样一个类 <com.silvia.MyClass , MyClassLoader>
我们知道CLASSPATH下的类是由Application classloader 系统加载器 加载的,所以如果com.silvia.MyClass放在CLASSPATH下可以被加载
这样就可以有两个com.silvia.MyClass类啦
但是
能不能加载java.lang.Object这种类呢
不能。JVM里面做了相关安全认证。从而防止不可靠的甚至恶意的代码代替父加载器的可靠代码。
所以说一般来说我们应该覆写findClass方法就好了,不要绕过父类委托机制(不要覆写loadClass)
《疯狂JAVAj讲义》
《深入理解JAVA虚拟机》第七章虚拟机加载机制
《传智播客Java底层公开课视频》教学视频
参考:
一、虚拟机的类加载机制
加载 连接 初始化 ------------> 程序运行期完成
坏处: 好处:
性能开销 灵活性大大增强
二、虚拟机加载的过程如下
三、加载之后在内存中的样子
假设现在有这样一个类
[java] view
plaincopy
public class Demo {
public static void main(String[] args) {
new Person();
}
}
class Person{
private int age;
public Person()
{
System.out.println("this is Person Construct Method");
}
}
class Animal{
public static String name;
}
它在经过加载之后大致在内存中就如下:(非官方图,个人意淫)
至于new一个类之后在堆中内存怎么存放,咱们下节再细说
四、过程5初始化
自动收集1.类变量赋值,2静态语句块 形成<clinit>方法,各语句的顺序按照在类文件中出现的顺序
虚拟机会保证一个类的<clinit>()方法在多线程环境被正确的加锁、同步。
如果多线程同时初始化一个类,只会一个线程执行<clinit>()方法,其他线程阻塞等待。执行的线程执行结束后,其它线程唤醒之后不会再进入<clinit>()。同一个类加载器下,一个类只会初始化一次。
五、过程1加载:加载器
过程1由一个特定的组件进行完成:类加载器
除了加载阶段(图1的阶段1)用户应用程序可以通过自定义加载器参与,剩下的动作完全由虚拟机主导控制
也就是说类加载器的工作就是“通过一个类的全类名来获取描述此类的二进制字节流”
有三类加载器:参见我的上一篇博客:
深入理解jre,classload,类加载过程
ClassLoader是一个抽象类[java] view
plaincopy
public abstract class ClassLoader
JVM中除根加载器之外的所有加载器都是ClassLoader子类的实例
个人认为它大致有两类方法
1.静态的工具方法
比如
可以通过ClassLoader.getSystemClassLoader();获得AppClass Loader(系统类加载器)
[java] view
plaincopy
@CallerSensitive
public static ClassLoader getSystemClassLoader()
getParent()获取该加载器的父类加载器
[java] view
plaincopy
@CallerSensitive
public final ClassLoader getParent()
2 用于加载类的方法
我们通常通过继承这个类来实现我们的自定义类加载器
1)public的loadClass方法,对外提供的入口,进行加载类
[java] view
plaincopy
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
是ClassLoader类的入口点
为什么叫入口点:
比如我们自己写的OurClassLoader类
OurClassLoader ourCL=new OurClassLoader ()
Class<?> clazz=ourCL.loadClass("OurClass");
从而加载类并得到它的Class对象
2)剩下的基本上都是protected方法
通过覆写这些protected方法定制我们的功能
loadClass(String name,boolean resolve),注意这个是protected方法,不是外部直接使用的(不是你想调用就调用的),我们可以覆写这个方法
[java] view
plaincopy
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;
}
}
它会执行如下步骤
用findLoadedClass(String)来检查是否已经加载类,如果已经加载则直接返回
在父类加载器上调用loadClass()方法。如果父类加载器为null,则使用根类加载器来加载
如果父类加载失败,则调用findClass(String)方法查找类
findClass(String name)
[java] view
plaincopy
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
我们要覆写这个方法来自定义自己的加载类的行为
在这个方法中我们应该调用defineClass方法来把字节码转为Class对象
defineClass()
方法负责将字节码分析成运行时的数据结构,并检验有效性。 此方法是final型,我们也无法重写。
六、附加:父类委托机制
通过上面的过程我们能看到loadClass的确采用了父类委托和缓冲机制
双亲委派模型见我上一篇文章
那么问题来了:如果我们覆写loadClass方法呢,成功避开父类委托和缓冲机制(为什么这么任性),那能加载一个系统中已经存在的类吗?
可以,因为:
JVM是如何判定两个 Java 类是相同的: A.类的全名 B.加载此类的类加载器
我们自定义的一个新的类加载器,则加载这样一个类 <com.silvia.MyClass , MyClassLoader>
我们知道CLASSPATH下的类是由Application classloader 系统加载器 加载的,所以如果com.silvia.MyClass放在CLASSPATH下可以被加载
这样就可以有两个com.silvia.MyClass类啦
但是
能不能加载java.lang.Object这种类呢
不能。JVM里面做了相关安全认证。从而防止不可靠的甚至恶意的代码代替父加载器的可靠代码。
所以说一般来说我们应该覆写findClass方法就好了,不要绕过父类委托机制(不要覆写loadClass)
相关文章推荐
- Spring-MVC接收request参数和向页面传值总结
- JAVA几种IO工作机制及特点(二)
- myeclipse8.5 maven junit报ClassNotFoundException
- java工程中一些js问题总结
- JAVA并发处理经验(二)JAVA线程池运用与并发工具
- eclipse svn
- java开发ping一个地址,返回是否连通
- java时间转换
- Java和C#的异同
- MyEclipse更换背景主题方案
- 【第九章】 Spring的事务 之 9.4 声明式事务 ——跟我学spring3
- AJAX省市区三级联动下拉菜单(java版)
- java(12)--小应用-基于xml的简单考试查询系统
- java ftp 上传文件
- Android Eclipse 修改默认查看图片的打开方式
- springmvc拦截器
- java中synchronized的使用方法与具体解释
- 文本处理
- 标签简化Spring-MVC配置
- java读取文件