松散了好久 回归状态 系统学习JVM 记个笔记 写点自己的理解
注: 博主所有博客内容的学习笔记都是从学习资料处学习得来,些许位置的思路会借鉴,但保证所有文章的所有内容(包括文字和图) 都是自己原创字是一个一个打的 图是一笔一笔画的
简单画了个图 这是个简图 之后学习的内容会在深入的画详细图
其实就是JVM运行的第一步 讲class文件加载的过程
这个过程分为三步 加载 链接 初始化
也就是简图中的第一步
加载都做了什么?
1.通过类的全限定名获取定义此类的二进制字节流
----> 其实就是把磁盘上 通过全类名找到这个类 然后加载到内存中
2.将这个字节流所代表的静态存储结构转化为[方法区]的运行时数据结构
--->这个方法区是一个概念性的内容 JDK8以前叫永久代 JDK8及以后是元空间
3. 在内存中生成一个java.lang.Class对象作为方法区这个类的各种数据的访问入口
--->这个加载的类就是我们用反射获得的类的class对象了
链接link
链接阶段一共分为三个步骤
1.验证
·目的在于确保class文件的的字节流包含信息符合当前虚拟机的要求 保证被加载类的正确性
不会危害JVM的安全
--->比如说最开始的魔法值 CAFEBABE就是所有字节码文件的开头 可能这就是程序员的浪漫~
16进制一共就6个字母 竟然就能组出来一个词
2.准备
·为变量分配内存并设置默认初始值 比如int是0 引用是null 等等
·这里不包含static final 因为这样的属于常量 在编译的时候就分配了 准备阶段会显示初始化
`这里不会为实例变量分配初始化 以为这是类加载 变量在方法区中 而实例变量会随着对象一起分配到java堆中
---> 例: public static int a = 1; //在准备阶段只会赋值为0 在初始化之后才会是1
3.解析
· 将符号引用转换为直接引用的过程
·其实解析操作常常是伴随着JVM在执行完初始化之后再执行
初始化init
`初始化阶段就是执行类构造器方法()方法的过程
·此方法不需要定义 是javac编译器自动收集类的所有变量赋值动作和静态代码块中的语句合并而来
·构造器中的指令按照语句在源文件的位置顺序执行
· ()不同于类的构造器 构造器是虚拟机视角下的()方法
·若该类具有父类 则保证该类执行之前 父类的()方法已经执行完毕
·虚拟机必须保证一个类的()方法在多线程环境下加锁同步
--->也就是说如果你没有给属性赋值的语句 JVM视角里就不会有方法
---->有几个构造方法就有几()方法
---->保证一个类在程序中唯一
一张图解释以上说的东西
三张图解释clinit的同步问题
其实我们都知道类加载器有四种
但是JVM规范定义分类只有两种
引导类加载器
自定义类加载器
我们平时经常说的四种
引导类加载器(bootstrap Class Loader). 扩展类加载器(Extension Class Loader) 系统类加载器(System Class Loader) 自定义加载器(User Defined Class Loader)为什么说只有两种?
--->JVM规范 所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
--->其实还有一种分法 就是Bootstrap ClassLoader是由c和c++开发的
--->其他所有类加载器都是我们的java编写
---> Extension Class Loader System Class Loader User Defined Class Loader
以上三种都直接或间接继承了ClassLoader类
重点说明!!
类加载器不是继承体系 不是上层下层 也不是父子继承 而是包含关系
换句话是是有等级制度的 如果上级没有找到资源 才会分给下级 如果上级找到了 那就不需要下级负责
//获得系统类加载器 sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获得系统类加载器的父类 sun.misc.Launcher$ExtClassLoader@1540e19d
//得到扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);
//获得扩展类加载器的父类 null
//但是得不到 应该是引导类加载器
ClassLoader bootStrapClassLoader = extClassLoader.getParent();
System.out.println(bootStrapClassLoader);
//得到本类的类加载器 sun.misc.Launcher$AppClassLoader@18b4aac2
//得到系统类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//得到string的类加载器 null
//说明string是由引导类加载器加载的
ClassLoader stringClassLoader = String.class.getClassLoader();
System.out.println(stringClassLoader);
BootStrapClassLoader 引导类加载器
`这个类加载使用c/c++语言实现,嵌套在JVM内部
`他用来加载java的核心库 (JAVA_HOME/jre/lib/rt.jar、resources或sun.boot.class.path路径下的内容)
用于提供JVM自身需要的类
`并不继承ClassLoader 没有父加载器
·他来加载扩展类加载器和应用程序类加载器 并指定为他们的父加载器
·BootStrap启动类只加载包名为java javax sun开头的类
Extension ClassLoader 扩展类加载器
·java语言编写 由sun.misc.Launcher$ExtClassLoader实现(上文已经打印过了)
·派生于ClassLoader类
·父类是引导类加载器
·从java.ext.dirs系统属性所指定的目录中加载类库。或从JDK的安装目录jre/lib/ext子目录下加载类库。
如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
--->感觉这里其实可以玩一下小操作 把自己的类放在目录下 获得类加载器
AppClassLoader 应用程序类加载器(系统类加载器)
·java语言编写 sun.misc.Launcher$AppClassLoader实现
·派生于ClassLodaer类
·父类是扩展类加载器
·他负责加载环境变量classpath或系统属性 java.class.path执行路径下的类
·一般来说 java应用的类都由他加载
·通过ClassLoader.getSystemClassLoader()方法 可以获得
用户自定义类加载器
·在JAVA的日常应用程序开发中,类的加载几乎是由上面三个加载器互相配合完成的。
·在必要的时候还可以自定义类加载器
--->为什么要自定义类加载器?
1.隔离加载类 --->各种中间件为了防止各种包的冲突 需要隔离
2.修改类的加载方式
3.扩展加载源 ---->当我们不光需要从物理磁盘,网络中加载类。
可能需要从数据库中,甚至是电视机的机顶盒加载的时候 就可以自定义类
4.防止源码泄露 --->可以对字节码加密 加载器中解密
双亲委派机制
我想大家经常在面试题中看到相应内容 什么是双亲委派机制
双亲委派 工作原理
(1) 如果一个类加载器收到了加载类的请求,他不会立即加载,而会把这个请求委托给父类的加载器去执行
(2)如果上层加载器仍然有上层类加载器 则继续向上委托直到最终到达顶层类加载器
(3)如果父类加载器可以完成加载任务 就成功返回 如果无法完成 子类加载器才会尝试自己加载
以上就是双亲委派机制
//当我创建一个类 java.lang.String 和String同名同包的话
public class String {
public static void main(String[] args) {
System.out.println("这是String");
}
}
执行main方法
原因就是因为双亲委派 他将这个类交给了引导类加载器加载 那个string类没有main方法 所以他找不到
直接无视掉了我们创建的这个java.lang.String类
上文中 我们在创建java.lang.String 类 默认直接创建了jdk自带的String 直接无视掉了我们创建的类
这样可以对java核心代码保护 也就是沙箱安全机制
个人理解: 类似于把想要隔离的内容放在一个箱子里 这个箱子里的操作影响不到其他位置 有点像容器
比如说战争。 战争里面打打杀杀不影响普通百姓的生活。。 例子可能不太恰当 。。 理解一下
其他
问:如何判断两个Class对象是否为同一个类?
答:类的完整包名一致 并且类的ClassLoader同样一致
java中 类的主动使用和被动使用
主动使用分为七种情况
(1)创建类的实例
(2)访问某个类或接口的静态变量 或者对静态变量赋值
(3)调用类的静态方法
(4)反射 Class.forName("com.gxz.AAA")
(5)初始化一个类的子类
(6)JVM启动时被标明启动类的类
(7)JDK7开始提供动态语言支持 :
java.lang.invoke.MethodHandle 实例的解析结果:
REF_getStatic、 REF_putStatic 、REF_invokeStatic 句柄对应的类没有初始化 则初始化
对于(7)说点自己的理解 因为类似于把java文件编译成字节码文件一样 MethodHandle同样可以解析内容
如果解析结果是REF*** 那些内容 运行的时候则会动态执行(也许动态代理的写出的字节码也是这样?)
个人浅见
除了以上七种情况 其他都被视为被动使用 被动使用不会对类进行[初始化] 这个初始化就是加载过程中的第三步