明天你会感谢今天奋力拼搏的你。
ヾ(o◕∀◕)ノヾ
Java程序运行原理
Java编译器(前端编译器,javac就是其中一种)把Java源代码编译成Java虚拟机识别的字节码class文件。
Java虚拟机就相当于一个字节码的执行引擎,Java虚拟机有多种,但都需要符合Java虚拟机规范。
Java虚拟机中主要采用解释编译
《Java虚拟机规范》描述Java虚拟机的规范。
《Java语言规范》描述Java语言的规范和特性。
Java语言和虚拟机规范官方文档:
https://docs.oracle.com/javase/specs/index.html
JVM运行时数据区

线程共享:所有线程能访问这块内存数据,随虚拟机或者GC而创建和销毁
线程独占:每个线程都会有它独立的空间,随线程生命周期而创建和销毁
虚拟机中垃圾回收,主要就是对线程共享部分进行回收
方法区
作用:存储加载的类信息、常量、静态变量、JIT编译后的代码等数据。(即存储的类的元信息)
JDK7中的永久代和方法区是否等价?JDK1.8方法区就没有了?
不等价,方法区是Java虚拟机规范中定义的。只是JDK7中通过永久代实现,JDK8中通过元数据区实现。
回收主要针对常量池的回收,和类型的卸载;
当方法区无法满足内存需求时,报OOM。
类信息可以理解为Java反射中的XXX.class中的数据。
方法区中的类什么时候会被回收?
条件1:class对应的实例都被回收了。
条件2:加载类的ClassLoder 被回收了。
条件3:类已经没有被其他实例引用。
存在方法区中的类信息有什么呢?
常量池、方法字节码指令、堆内存中的对象引用、其他
堆内存

作用:唯一的目的就是存放对象实例,几乎所有的对象、数组都在这儿离存放
堆内存中存的是实例字段(field)、数组的元素。
方法区存的是类信息、堆内存中存的是对象的数据。
对于大多数应用来说,堆是JVM管理的内存中最大的一块内存区域,也是最容易OOM的区域。
大多数JVM都将堆实现为大小可扩展的(通过-Xmx、-Xms控制)
有的概念中会说堆内存中分为新生代和老年代,方法区有有永久代。
方法区、堆内存是Java虚拟机规范中的概念。
而新生代和老年代是Hotspot实现垃圾回收(GC)算法时提出的概念。
永久代也是方法区实现中提出的一些概念,JDK1.7实现方法区用的是永久代,JDK1.8用的是元数据区。
堆中的对象什么时候被回收?怎么知道堆中的对象不再被使用了
对象释放
怎么判断对象可以被释放?
1、可以引用计数器算法,在对象中保存一个计数器记录引用数,但存在两个对象相互引用的问题。(所以Java和C#里都不是通过引用计数器实现的)
2、通过可达性分析算法:主流的商用程序语言(Java、C#)都是通过可达性分析算法来判定对象是否存活的。
如下图,判断对象通过引用是否可达Gc roots,如果不可达则回收,可达则保留。
GC Roots可以是:

然后对线程独占的的内存区域进行分析,里面有什么?
虚拟机栈

虚拟机栈:线程中Java方法执行的模型,每个方法执行时,就会在虚拟机栈中创建一个栈帧,每个方法从调用到执行的过程,就对应着栈帧在虚拟机栈中从入栈到出栈的过程。
栈帧:虚拟机栈由多个栈帧(Stack Fframe)组成。
一个线程会执行一个或多个方法,一个方法对应一个栈帧。
怎么理解虚拟机栈是线程中方法执行的模型?
比如:主线程中的main方法、线程new Thread执行的run方法,通俗一点就是线程在执行一个个方法。所以也可以理解为虚拟机栈就是一个线程执行的模型。
所以虚拟机栈是怎么运行的?
比如一个简单的Java程序,先执行main方法,然后main方法中调用了test方法,那么虚拟机栈中会先入栈main方法的栈帧,然后继续入栈test方法栈帧。然后test方法执行完后,test栈帧出栈,然后main方法栈帧出栈。
如上也能发现,通过递归方式循环调用方法存在弊端,会让虚拟机栈消耗过多资源。
所以尽量不用递归,递归的逻辑一般都可以通过while实现。
局部变量先是存在方法区中的一个字节码指令,方法使用时,方法栈帧加入虚拟机栈中执行才给局部变量赋值(八大基本类型的默认初值是在方法区中),先把初值放入操作数栈,然后赋值给局部变量表中对应变量。
栈帧中还有什么?
https://www.cnblogs.com/jhxxb/p/11001238.html
指令的参数必须来自操作数栈,而不能来之局部变量表。指令的执行结果也要先放入操作数栈,然后再存入局部变量表。设计就是这样。
以前刚学的时候老师所说的堆栈,栈可能指的就是局部变量表。
动态链接,链接的是什么?
方法区中存了类的信息,类的信息里当然包含有方法的信息(方法的二进制字节码指令),动态链接就是可以帮助找到此方法的二进制字节码指令。执行方法其实在虚拟机中就是执行这些二进制字节码指令。
返回值地址的作用?
方法执行完后出栈,返回值地址告诉当前方法,出栈后返回到哪里去(上一个方法里)。
本地方法栈

本地方法栈:和虚拟机栈功能类似,虚拟机栈是为虚拟机执行Java方法而准备的,本地方法栈是为虚拟机使用Native本地方法而准备的。
《Java虚拟机规范》中对Native方法使用的语言、使用方式与数据结构没有强制规定。
HotSpot虚拟机中虚拟机栈和本地方法栈合二为一。
程序计数器
程序计数器(Program Counter Register)记录当前线程执行字节码的位置,字节码解释器工作时,就是通过改变计数器的值来选取下一条需要执行的字节码指令。
如果执行Java方法,存储的是字节码指令地址,如果执行native 方法,则计数值为空。
程序计数器是唯一一个在《JVM规范》中没有任何OOM的区域。
程序计数器用来记录方法中的字节码指令执行到哪个位置了。
程序计数器记录的是字节码指令的内存地址。
为什么要记录?
因为多线程中CPU会切换线程。
直接内存不是JVM运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域,是我们常说的堆外内存。
JDK1.4引用NIO,它可以使用Native函数库,直接分配堆外内存,然后通过一个存在Java堆中德尔DirectByteBuffer对象作为操作这块内存区域的引用进行操作。
直接内存不受Java堆大小限制,当内存总和大于及其物理内存时,会OOM。
JIT 即时编译
https://www.jianshu.com/p/169d6a50284a

Java类文件结构
示例代码:思考这一段代码,如何在JVM中执行?
public class Demo1 {
final int NUMBER = 20;
private static Student stu = new Student();
public static void main(String args[]){
int x = 500;
int y = 100;
int a = x/y;
String envName = "JAVA_HOME";
stu.age = a;
String path = System.getenv(envName);
}
}
class文件编译之后,可以通过javap -v -p Demo1.class 把class反编译出来,查看其中的指令。
Class文件结构
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4..3.2
class文件包含JAVA程序执行的字节码;数据严格按照格式紧凑排列在class文件中的二进制流(以16进制来描述,如下图),中间无任何分隔符;u1、u2、u4(4个字节的数字)、u8都是无符号数(数字),info结尾都是表,要搞懂这个PPT,可以看《深入理解Java虚拟机》第六章。


看右边的表,u4表示4个字节的数字,对应magic;(魔数),左边前4个字节 ca fe ba be(咖啡贝比),也就是Java虚拟机会首先看前面4个字节来识别是否为虚拟机支持的class类文件。
后面2个字节就是小版本号 minor_version
继续2个字节就是主版本号,00 34 转换成10进制是52,是JDK的版本号。JDK1.6是50,1.7是51,1.8是52
继续又是个u2,constant_pool_count,表示常量池的count,左上图是 00 1b转换10进制是27。
继续cp_info表示具体的常量,constant_pool_count值有多少,后面就会接上这些常量。这些常量占多少个字节类似如下图表所示,这里只有部分。

TIP:常量池不只是Java代码里定义的常量,类名称、方法名称等都会存在常量池中。
access_flags:访问标记符,有类的访问修饰符,还有方法的访问修饰符,如下图表所示类的访问标志:

如果是public final则把上图中两个数字相加。
this_class:当前类的名字(值是一个常量池的常量,CONSTANT_Class_info型常量的结构)
super_class:父类的名字
interface_count:实现的接口数
interface:接口数有多少个,后面会有多少个接口也是CONSTANT_Class_info型常量的结构

fields_count:字段数
fields:字段表结构,如下图:

methods_count:方法数
methods:方法

名词解释
全限定名:把类名中的.替换成/,例如com.cyx替换为com/cyx
简单名称:是指没有类型和参数修饰的方法或者字段,如public int test(){},简单名称就是inc, public int age字段的简单名称就是age。

描述符:比较复杂,描述符的偶用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。如“int[]”将被标记为“[I”java.lang.String[][] ,被标记为“[[Ljava/lang/String;”;java.lang.StringtoString() 的描述符为()Ljava/lang/String
属性表集合
在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
不像其他表,属性表的排列没有严格的顺序,只要不与已有属性名重复即可,
任何人实现的编译器都可以向属性表中写入自己的属性信息,但JVM只认识自己预定义的21种属性,JVM会忽略不认识的属性。
详见《深入理解Java虚拟机》P180 表6-13
class文件运行
一个Ojbect在JVM所占空间多大?
64位操作系统,开启指针压缩,指针头16个字节,开启指针压缩12字节,
然后会加上Java中8个基本类型:boolean(1)、byte(1)、short(2)、char(2)、int(4)、float(4)、long(8)、doubble(8)
即:12+1+1+2+2+4+4+8+8=42个字节。然后会进行字节对齐,填充6位,42+6=48字节。
全部评论