说点什么呢,java比你想的要难
写了多年java,发现好多人并不知道一个class文件怎么被解析执行的,所以我也发表下看法
1. 编写java源文件
2. 把java源文件编译成.class字节码文件,JVM不认识源文件
3. JVM处理class文件
搞java开发,不得不提的就是JVM了,JVM全称是Java Virtual Machine(简称JVM,中文叫Java虚拟机,请务必记住JVM,看到不少人整天JVM的,都不知道它的全称是什么),java的宿主环境,可以认为JVM就是虚拟仿真出来的一台计算机。简单绘了一张图,如下(一图胜千言):
java之所以一次编写,到处运行,就是因为虚拟机(虚拟虚拟,虚拟出来的计算机,一台被托管的电脑)的缘故。
3.1 jvm处理class文件
加载是指将java源文件编译之后的class文件读入到内存中,然后在堆区创建一个java.lang.Class对象,用于封装类在方法区内的数据结构。类加载的最终目的是封装类在方法区的数据结构,并向java程序员提供访问方法区数据的接口。
类的生命周期一共分为5个阶段,加载、连接、初始化、使用、卸载。
加载:类的加载过程主要完成3件事件,1.通过类的全限定名来获取定义此类的二进制字节流,2.将这个类字节流代表的静态存储结构转为方法区的运行时数据结构,3.在堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口。这个过程主要是类加载器完成的,如图
从上图我们就可以看出类加载器之间的父子关系和管辖范围。
BootStrap是最顶层的类加载器,它是由C++编写而成,并且已经内嵌到JVM中了,主要用来读取Java的核心类库JRE/lib/rt.jar
ExtensionClassLoader是是用来读取Java的扩展类库,读取JRE/lib/ext/*.jar
AppClassLoader是用来读取CLASSPATH指定的所有jar包或目录的类文件
甚至可以自定义加载器
加载过程用到了很牛掰的双亲委派模型,它是这样的一套机制:
"类加载器"加载类时,先判断该类是否已经加载过了;
如果还未被加载,则首先委托其"类加载器"的"父类加载器"去加载该类,这是一个向上不断搜索的过程,当类所有的"祖宗类加载器"(包括了bootstrap classloader)都没有加载到类,则回到发起者"类加载器"去加载,如果还加载不了,则抛出ClassNotFoundException.
连接:这个过程分3个阶段(校验,准备,解析)完成。首先是校验,此阶段主要校验class文件包含的信息是否符合jvm的规范。具体的校验通过对文件格式,元数据,字节码,符号引用验证来完成。然后是准备,此阶段为类变量分配内存,并将其初始化为默认值。最后是解析,即把类型中的符号引用转换成为直接引用。具体的解析有4种,1.类或接口的解析,2.字段解析,3.类方法解析,4.接口方法解析。完成这3个阶段就完成了类的连接。
初始化(很重要):即执行类的构造器方法的过程。有5种方法可以完成初始化:1.调用new方法,2.使用Class类的newInstance方法(反射机制),3.使用Constructor类的newInstance方法(反射机制),4.使用Clone方法创建对象,5.使用(反)序列化机制创建对象
码农开发用new关键字创建对象,而框架(spring,mybatis等)特别喜欢用第2和3种方式创建对象,还记得我说的框架四要素吗,其中有一要素就是反射机制
使用:完成类的初始化后,就可以对类进行实例化,在程序中进行使用了
卸载:当类被加载,连接和初始化后,它的生命周期就始了,当代表类的class对象不在被引用时,class对象就会结束生命周期,类在方法区内的数据就会被卸载。因此一个类何时结束生命,取决于代表它的class对象何时结束生命。
3.2 JVM的内存结构
Java程序在运行时,需要在内存中的分配空间。为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式,如上图,Java中的内存分配了5个区:
Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。
静态变量+常量+类信息+运行时常量池存在方法区中,实例变量存在堆内存中。
Heap 堆:堆这块区域是JVM中最大的,应用的对象和数据都是存在这个区域,这块区域也是线程共享的,也是 gc 主要的回收区,一个 JVM 实例只存在一个堆类存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行。
注jdk8永久代改为了metaspace元空间
Stack 栈:栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的, 基本类型的变量和对象的引用变量都是在函数的栈内存中分配。遵循“先进后出”/“后进先出”原则。
PC Register程序计数器
每个线程都有一个程序计算器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
Native Method Stack本地方法栈
它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。
暂且这么多,以后接着补充。。。。。。
参考:
0. The Java® Language Specification(8) https://docs.oracle.com/javase/specs/jls/se8/html/index.html
1. The Java® Virtual Machine Specification (8) https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
我想于java码农而言,没有比这这两个官方上千页的规范更重要的了,看清楚了:一个是Java语言规范,一个是Java虚拟机规范
当然还有其他规范为了方便码农开发
2. Advanced Java Bytecode Tutorial https://www.jrebel.com/blog/java-bytecode-tutorial
3. 深入JVM:ClassLoader相关知识简介 https://developer.51cto.com/art/201009/227269.htm
4. Java Class Loader https://javapapers.com/core-java/java-class-loader/
5. Understanding the Java ClassLoader https://www.ibm.com/developerworks/java/tutorials/j-classloader/j-classloader.html
6. Advanced Java Class Tutorial: A Guide to Class Reloading https://www.toptal.com/java/java-wizardry-101-a-guide-to-java-class-reloading
7. 看完这篇文章你还敢说你懂JVM吗? https://virtual.51cto.com/art/201901/591418.htm?mobile
8. JVM internals https://blog.jamesdbloom.com/JVMInternals.html#jvm_system_threads
9. Difference between initializing a class and instantiating an object?
10. class-loader-subsystem-jvm-internals https://codepumpkin.com/class-loader-subsystem-jvm-internals/
参考书籍:
0. Java编程思想 (第1,,2,5,8,14章节)
1. Virtual Machines
模仿一台计算机
来源:oschina
链接:https://my.oschina.net/u/4312838/blog/4273215