一、为什么要设计虚拟机?
在实际开发中使用c语言开发一个应用程序,分别运行在两套系统上的话,需要修改的地方不只是一个两个函数,这是一个很庞大的工作量,而且你还需要精通两套系统相应的接口才能修改。
以上通过c语言来解释了不可跨平台的概念,明白了不可跨平台的概念,那么跨平台的·概念自然也就清楚了,跨平台语言就是说我一旦写了一行代码,无论把这行代码移植到什么系统的电脑上都能正常执行输出相同的结果。java就是这么一种语言。那么java是怎么解决上面的问题的呢?怎么就能让windows和Linux系统都能识别它的语句的呢?这一切还得归功于java虚拟机。首先要想让不同的系统都能识别Java代码,那么我们必须得设计一种中间语言,这个中间语言的作用就是把java语言翻译成不同系统所能识别的语言,那么虚拟机是干什么的?虚拟机就是执行这些中间语言指令的,通过虚拟机执行完中间语言指令之后,中间语言就变成了不同平台上的PAI调用。
java源代码,通过编译器编译,产生中间语言指令,虚拟机编译中间语言指令,生成特定平台匹配的指令并运行。
二、什么是虚拟机?
虚拟机就是一台虚拟的机器,就是一款软件,用来执行一系列虚拟计算机指令
系统虚拟机:对物理计算机的仿真 VMvare
程序虚拟机:专门为执行单个计算机程序而设计 Java虚拟机,
三、什么是JVM?Java Virtual Machine
Java虚拟机是一台能执行二进制字节码的虚拟计算机,其运行的字节码未必有Java语言编译而成
JVM平台的各种语言可以共享Java虚拟机带来的 跨平台性,垃圾回收器,即时编译器
一次编写到处运行
自动内存管理
自动垃圾回收
优点:无需开发人员手动参与内存的分配与回收,更专注于开发
缺点:弱化了Java开发人员在程序出现内存溢出时定位问题和解决问题的能力
四,JVM的位置
JVM位于操作系统上层,但是位于应用程序下层。
五,JVM的体系结构
- 线程共享:方法区、堆
- 线程私有:java栈、本地方法栈、程序计数
入口是编译好的字节码文件(编译器前端)-->经过类加载子系统(将我们的字节码加载到内存当中,生成一个class对象,中间经过三步:加载--->链接--->初始化)
加载,通过一个类的全限定名获取该类的二进制字节流,在内存中生成代表该类的class对象
连接,连接又包含三块内容:验证、准备、初始化。
(1)验证,确保class文件中的字节流中包含信息符合当前虚拟机要求,保证类加载的正确性
(2)准备,为类变量分配内存并设置默认初始值,不会为实例变量分配
(3)解析,把常量池中的符号引用转换为直接引用,通常在JVM初始化之后再执行
初始化,执行类的clinit()方法(类变量赋值动作,静态代码块中的语句)
程序计数器 :用来存储下一条指令的地址,没有GC OOM
虚拟机栈是什么?
保存局部变量,对象地址的引用,部分结果 参与方法的调用和返回
每个线程在创建时都会创建一个虚拟机栈,其内部是一个个栈帧,一个栈帧对应一个方法
生命周期和线程一致
StackOverFlowError:栈溢出异常
线程请求分配的栈容量超过Java虚拟机栈允许的最大容量(栈太小满足不了栈帧)
Out of Memory Error:内存溢出异常
线程创建时,没有足够内存去创建对应的虚拟机栈(内存太小满足不了栈)
本地方法接口库
使用native修饰的方法,有方法体(没显示出),不是用Java实现的
native可以与public 共用,不可以与abstract共用
为什么会有本地方法?
Java虚拟机需要与Java外界的环境交互,本地方法就是一种交流机制,与操作系统进行交互,操作系统底层使用C/C++编写的
本地方法栈
Java虚拟机栈是用于管理Java方法的调用
本地方法栈用于管理本地方法的调用
与虚拟机栈类似
当某个线程调用本地方法时,它就进入了一个全新的且不受虚拟机限制的世界,可以直接使用本地处理器中的寄存器,本地内存(执行效率高)
堆【线程共享】 :
Java堆区在JVM启动的时候即被创建,堆内存大小可以调节
逻辑上是连续的,在物理空间上可以是不连续的。
存储:对象实列 数组
所有线程共享堆,堆中划分出线程私有的缓冲区TLAB(Thread Local Allocation Buffer TLAB线程本地分配缓存)JVM为每一个JAVA线程在Eden区中分配一块TLAB空间
1.堆是线程共享的,并发环境下,从堆划分内存,线程不安全,加锁会影响分配速度,于是就有了TLAB
2. TLAB作为内存分配的首选,仅占Eden的1%,分配失败,则采用加锁机制
方法结束后,堆中的对象不会马上被移除,在垃圾收集时才会被移除
堆是GC的重点回收对象,频繁GC会影响用户线程
JDK1.7前堆内存:新生区+养老区+永久代
JDK1.8后堆内存:新生区+养老区+元空间
在Hotspot中,Eden空间和另外两个survivor空间缺省所占的比例是8:1:1
当然开发人员可以通过选项“-XX:survivorRatio”调整这个空间比例。比如-xX:survivorRatio=8
几乎所有的Java对象都是在Eden区被new出来的。
绝大部分的Java对象的销毁都在新生代进行了。
对象分配一般过程
new的对象先放伊甸园区。此区有大小限制。
当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区
然后将伊甸园中的剩余对象移动到幸存者0区。
如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会放到幸存者1区。(S1,S0:复制之后交换,谁空谁是to)
如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区。
啥时候能去养老区呢?可以设置次数。默认是15次。可以设置参数:-XX:MaxTenuringThreshold=<N>进行设置。
方法区
加载的类信息、常量、静态变量、编译后的代码
HotSpot虚拟机设计团队选择吧收集器的分代设计扩展至方法区(或使用永久代来实现方法区)
缺点:容易出现内存溢出,Java虚拟机内存有上限
JDK1.8 元空间代替永久代
元空间不在虚拟机设置内存,在本地方法中实现内存
六、双亲委派机制
一个类加载器收到一个类加载请求时,会把请求委派给父类加载器。
举例:自定义一个Java.lang.String类,其中定义一个main方法,加载该类时,委托给父类,父类中的String类中无main方法,报出类Java.lang.String中找不到main方法
优点:1. 避免类的重复加载
2. 保护程序安全,防止核心API被随意篡改
沙箱安全机制: 限制程序运行环境
限制系统资源访问,不同的沙箱访问权限不同
对代码隔离,防止被本地系统造成破坏
执行引擎:解释器(解释运行),jit及时编译器(编译器后端),垃圾回收器三部分。
6,Java代码的执行流程
高级语言翻译为机器指令,主要是由执行引擎完成的。
解释器(解释运行,把字节码翻译为机器指令,主要负责翻编译器性能)。
jit及时编译器(编译器后端,主要是把热点代码缓存起来,主要负责编译器性能)组成执行引擎。