iis网站重定向,网站制作方案大全,展示营销型网站,外网登录 wordpress一、什么是JVM
1、什么是JDK、JRE、JVM
JDK是 Java语言的软件开发工具包#xff0c;也是整个java开发的核心#xff0c;它包含了JRE和开发工具包JRE#xff0c;Java运行环境#xff0c;包含了JVM和Java的核心类库#xff08;Java API#xff09;JVM#xff0c;Java虚拟…一、什么是JVM
1、什么是JDK、JRE、JVM
JDK是 Java语言的软件开发工具包也是整个java开发的核心它包含了JRE和开发工具包JREJava运行环境包含了JVM和Java的核心类库Java APIJVMJava虚拟机它是运行在操作系统之上的它与硬件没有直接的交互
2、说的通俗点JVM到底是什么
JVM 是一个虚拟的计算机但它并不是真正的物理机器而是在你的电脑上运行的一个软件。它的主要任务是执行 Java 程序。你可以把它想象成一个翻译官负责把 Java 程序的代码翻译成你的电脑能够理解并执行的指令。
Java 语言有一个著名的口号“一次编写到处运行”。所谓“一次编码随处运行“正是基于不同系统下的jvm帮你掩盖了系统之间接口的差异
jdk是开发人员的工具包它包含了java的运行环境和虚拟机而一次编写到处运行就是基于jvm
3、总结
JVM就是一套软件不管在什么平台上都可以安装安装好之后就可以运行我们的Java程序并且是在任意平台上都可以平台之间的接口差异那都是JVM去做的无需我们关心。
二、JVM整体架构
1、Java程序如何被运行的 1.源码编译通过Java源码编译器将Java代码编译成JVM字节码.class文件
2.类加载通过ClassLoader及其子类来完成JVM的类加载
3.类执行字节码被装入内存进入JVM虚拟机被解释器解释执行
2、JVM模型 由上面的图可以看出JVM虚拟机中主要是由三部分构成分别是类加载子系统、运行时数据区、执行引擎。 类加载子系统 JVM把描述类的数据从Class文件加载到内存并对数据进行校验、转换解析和初始化最终形成可以被JVM直接使用的Java类型。 运行时数据区 JVM在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
这些区域有各自的用途以及创建和销毁的时间有的区域随着虚拟机进程的启动而一直存在有些区域则是依赖用户线程的启动和结束而建立和销毁。 执行引擎 执行引擎用于执行JVM字节码指令主要有两种方式分别是解释执行和编译执行区别在于解释执行是在执行时翻译成虚拟机指令执行而编译执行是在执行之前先进行编译再执行。
解释执行启动快执行效率低。编译执行启动慢执行效率高。
垃圾回收器就是自动管理运行数据区的内存将无用的内存占用进行清除释放内存资源。 本地方法库、本地库接口 在jdk的底层中有一些实现是需要调用本地方法完成的使用c或c写的方法就是通过本地库接口调用完成的。比如System.currentTimeMillis()方法。、
三、class到底长什么样子
这是我们的测试案例
/*
* 基本类结构
* */
public class ClassStruct {private static String name JVM;private static final int age 18;public static void main(String[] args) {System.out.println(Hello name);}}
. java文件编译之后就会产生一个.class文件
我们将上面这个编译后的.class文件打开就长下面这样 class文件是一个二进制文件转化后是16进制展示实际上class文件就是一张表它由以下数据项构成这些数据项从头到尾严格按照以下顺序排列 下面我们对这些数据项逐一介绍
魔数
固定的CAFEBABE巧记咖啡宝宝 版本号
34换成10进制就是52 jdk的版本标记映射关系 可以看到就是采用的jdk8进行编译的
常量池
常量池记录了jvm内的一堆常量信息这部分由 【2个字节的常量池计数器】 【n个cp_info结构】组成 常量池中主要存放两大类常量字面量Literal和符号引用Symbolic References。 字面量比较接近于 Java 语言层面的常量概念如文本字符串、声明为 final 的常量值等。 而符号引用则属于编译原理方面的概念包括了下面三类常量 类和接口的全限定名Fully Qualified Name、字段的名称和描述符Descriptor、方法的名称和描述符
常量池计数器 标注后面有多少个对应个数的cp_info
CP_INFO
cp_info有多种类型
直接类型存的就是当前值这种像IntegerLong等长度都是确定的引用类型存的是指向其他位置的指针 我们可以看看真实的CP_INFO条目的内容
javap -v ClassStruct.class Utf8对应的就是CONSTANT_Utf8_INFOString对应的就是CONSTANT_String_INFO
其他信息
常量池之后是紧挨的一系列信息这些信息大同小异无非就是值、或者引用
访问标记public abstract 等信息类索引class类型最终指向一个utf8标记当前类的名字父类同上接口2字节记录数量后面记录多个接口类型接下来是字段、方法、属性都是2字节记录后面多少个后面紧跟对应的结构体类型
四、运行时数据区深度剖析
字节码只是一个二进制文件存放在那里。要想在jvm里跑起来先得有个运行的内存环境。
也就是我们所说的jvm运行时数据区。
1、运行时数据区的内存分布
运行时数据区是jvm中最为重要的部分执行引擎频繁操作的就是它。类的初始化以及后面我们讲的对象空间的分配、垃圾的回收都是在这块区域发生的。 根据《Java虚拟机规范》中的规定在运行时数据区将内存细分为几个部分
线程私有的Java虚拟机栈Java Virtual Machine Stack、程序计数器Program Counter Register、本地方法栈Native Method Stacks
大家共享的方法区Method Area、Java堆区Java Heap 接下来我们分块详细来解读每一块是做什么的如果溢出了会发生什么事情
2、程序计数器
程序计数器
每个线程一个。是一块较小的内存空间它表示当前线程执行的字节码指令的地址。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令所以整个程序无论是分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于线程是多条并行执行的互相之间执行到哪条指令是不一样的所以每条线程都需要有一个独立的程序计数器各条线程之间计数器互不影响独立存储我们称这类内存区域为“线程私有”的内存。如果是native方法这里为空
总结程序计数器就是记录着当前线程的所有指令以及当前执行到哪个指令了下一步该执行哪个指令
在虚拟机规范中没有对这块区域设定内存溢出规范也是唯一一个不会溢出的区域
因为它不会溢出所以我们没有办法给它造一个但是从class类上可以找到痕迹。
回顾上面javap的反汇编其中code所对应的编号就可以理解为计数器中所记录的执行编号。 3、虚拟机栈
JVM栈呢包含许许多多的栈帧每个栈帧包含四个部分局部变量、操作数栈、动态链接、方法出口。 线程私有每个线程都有自己的 JVM 栈线程之间不能共享栈中的数据。生命周期JVM 栈的生命周期与线程相同线程启动时创建线程结束时销毁。栈帧每个方法调用都会创建一个新的栈帧方法执行完毕后对应的栈帧会被弹出栈。 JVM栈呢通常会产生两种异常 StackOverflowError 当线程请求的栈深度大于 JVM 所允许的最大深度时抛出 StackOverflowError。常见原因包括递归调用过深、线程栈大小设置不当等。 OutOfMemoryError 当线程栈所需的内存超过 JVM 堆内存限制时抛出 OutOfMemoryError。常见原因包括线程数量过多、单个线程栈大小过大等。
4、本地方法栈
本地方法栈的功能和特点类似于虚拟机栈均具有线程隔离的特点不同的是本地方法栈服务的对象是JVM执行的native方法而虚拟机栈服务的是JVM执行的java方法虚拟机规范里对这块所用的语言、数据结构、没有强制规定虚拟机可以自由实现它甚至hotspot把它和虚拟机栈合并成了1个
和虚拟机栈一样也是两个
如果是创建的栈的深度大于虚拟机允许的深度抛出 StackOverFlowError
内存申请不够的时候抛出 OutOfMemoryError
5、堆区
与上面的3个不同堆是所有线程共享的所谓的线程安全不安全也是出自这里。
在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例Java世界里“几乎”所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的内存区域因此它也被称作“GC堆”这就是我们做JVM调优的重点区域部分。
堆区我们要分两个版本介绍jdk1.7和1.8版本的略有不同
1.7版本的堆区 Young 年轻区代 Young区被划分为三部分Eden区和两个大小严格相同的Survivor区 其中Survivor 区分为两个部分通常称为 S0 和 S1。每次GC 后Eden 区中存活的对象会被移动到其中一个 Survivor 区而另一个 Survivor 区则被清空。两个 Survivor 区轮流使用。 在Eden区间变满的时候 GC就会将存活的对象移到空闲的Survivor区间中根据JVM的策略在经过几次有一个阈值默认15垃圾收集后仍然存活于Survivor的对象将被移动到下面的Tenured区间。 Tenured 年老区 Tenured区主要保存生命周期长的对象一般是一些老的对象当一些对象在Young复制转移一定的次数以后对象就会被转移到Tenured区一般如果系统中用了application级别的缓存缓存中的对象往往会被转移到这一区间。 Perm 永久区 现在已经成为历史Perm代主要保存类信息class,method,filed等对象这部份的空间一般不会溢出除非一次性加载了很多的类
1.8版本的堆区
jdk1.8的内存模型是由2部分组成年轻代 年老代。永久代被干掉换成了Metaspace元数据空间
需要特别说明的是Metaspace所占用的内存空间不是在虚拟机内部而是在本地内存空间中(使用的是操作系统的内存)
Mataspace和永久区的区别 堆区的内存分配策略 对象优先在 Eden 分配 新创建的对象首先分配在 Eden 区。如果 Eden 区没有足够的空间会触发 Minor GC。 大对象直接进入老年代 大对象如长字符串或大数组可以直接进入老年代以减少新生代的碎片化。 长期存活的对象进入老年代 经过多次 Minor GC 仍然存活的对象会被晋升到老年代。 动态对象年龄判定 如果 Survivor 区中相同年龄的所有对象大小总和大于 Survivor 区的一半年龄大于或等于该年龄的对象可以直接进入老年代。
6、方法区
用于存储已被虚拟机加载的类信息、常量池、静态变量、即时编译器编译后的代码等数据。在 JDK 8 及之前的版本中方法区通常被称为永久代PermGen而在 JDK 8 及之后的版本中方法区被移到了元空间Metaspace
所以方法区只是一个逻辑概念存放在哪里由虚拟机自己去决定
五、类加载器
通过字节码我们了解了class文件的结构
通过运行数据区我们了解了jvm内部的内存划分及结构
接下来让我们看看字节码怎么进入jvm的内存空间各自进入那个空间以及怎么跑起来。 1、加载
类的加载就是将class文件中的二进制数据读取到内存中然后将该字节流所代表的静态数据结构转化为方法区中运行的数据结构并且在堆内存中生成一个java.lang.Class对象作为访问方法区数据结构的入口。 Java中有哪些类加载器呢
jvm提供了3个系统加载器分别是Bootstrp loader、ExtClassLoader 、AppClassLoader
这三个加载器互相成父子继承关系 Bootstrap加载器是用C语言写的它在Java虚拟机启动后初始化
它主要负责加载以下路径的文件 %JAVA_HOME%/jre/lib/*.jar %JAVA_HOME%/jre/classes/* -Xbootclasspath参数指定的路径
ExtClassLoader是用Java写的具体来说就是 sun.misc.Launcher$ExtClassLoader
ExtClassLoader主要加载
%JAVA_HOME%/jre/lib/ext/*ext下的所有classes目录java.ext.dirs系统变量指定的路径中类库
AppClassLoader也是用Java写成的它的实现类是 sun.misc.Launcher$AppClassLoader另外我们知道ClassLoader中有个getSystemClassLoader方法此方法返回的就是它。
负责加载 -classpath 所指定的位置的类或者是jar文档也是Java程序默认的类加载器
双亲委派机制
类加载器加载某个类的时候因为有多个加载器甚至可以有各种自定义的他们呈父子继承关系。
这给人一种印象子类的加载会覆盖父类其实恰恰相反
与普通类继承属性不同类加载器会优先调父类的load方法如果父类能加载直接用父类的否则最后一步才是自己尝试加载从源代码上可以验证。 我们看一下ClassLoader.loadClass()方法
protected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) { // 首先检测是否已经加载 Class? c findLoadedClass(name);if (c null) {//如果没有加载开始按如下规则执行long t0 System.nanoTime();try {if (parent ! null) { //重点父加载器不为空则调用父加载器的loadClass c parent.loadClass(name, false);} else { //父加载器为空则调用Bootstrap Classloader c findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) { }if (c null) {long t1 System.nanoTime(); //父加载器没有找到则调用findclass自己查找并加载c findClass(name); }}if (resolve) { resolveClass(c);}return c;}}为什么要有双亲委派机制 保证类的唯一性通过双亲委派机制确保了类的唯一性。例如Java 核心类库中的类如 java.lang.Object只会被启动类加载器加载一次避免了不同类加载器加载同一个类而导致的冲突和不一致问题。 防止类的重复加载每个类加载器都有自己的命名空间通过双亲委派机制确保了同一个类在不同的类加载器中只加载一次提高了类加载的效率减少了内存开销。 增强安全性双亲委派机制确保了核心类库的类不会被用户自定义的类加载器加载从而防止了恶意代码替换核心类库中的关键类增强了系统的安全性。
2、验证
加载完成后class里定义的类结构就进入了内存的方法区。
而接下来验证是连接阶段的第一步。实际上验证和上面的加载是交互进行的比如class文件格式验证。
文件格式的验证
这个好理解就是验证加载的字节码是不是符合规范
是不是CAFEBABYE开头主次版本号是否在当前jvm虚拟机可运行的范围内常量池类型对不对有没有其他不可识别的信息……等
元数据验证
到java语法级别了。这个阶段主要验证属性、字段、类关系、方法等是否合规
是否有父类除了Object其他类必须有是否继承了不该被继承的类比如final是不是抽象类是的话方法都完备了没字段有没问题是不是覆盖了父类里的final……等
字节码验证
最复杂的一个阶段。
等等字节码前面不是验证过了吗咋还要验证
上面的验证是基本字节表格式验证。而这里主要验证class里定义的方法看方法内部的code是否合法。
类型转换是不是有问题指令是否跳到了方法外的字节码上……
符号引用验证
最后一个阶段。
这个阶段也好理解我们上面的字节码解读时知道字节码里有的是直接引用有的是指向了其他的字节码地址。
而符号引用验证的就是这些引用的对应的内容是否合法。
utf8里记了某个类的名字这个类存在不方法或字段引用这些方法在对应的类里存在不存在类、字段、方法等上面的可见性是否合法……
3、准备
这个阶段为class中定义的各种类变量分配内存并赋初始值。
所做的事情好理解但是要注意几点
类变量 静态变量实例变量 实例化new出来的那些
理论上这些值都在方法区里但是注意方法区本身就是一个逻辑概念。
1.6里在永久代
1.8以后静态类变量如果是一个对象其实它在堆里。这个上面我们讲方法区的时候验证过。
//普通类变量在准备阶段为它开了内存空间但是它的value是int的初始值也就是 0
//而真正的123赋值是在类构造器也就是下面的初始化阶段
public static int a 123;//final修饰的类变量编译成字节码后是一个ConstantValue类型
//这种类型在准备阶段直接给定值123后期也没有二次初始化一说
public static final int b 123;
4、解析
解析阶段开始解析类之间的关系需要关联的类也要被加载。
这涉及到
类或接口的解析类相关的父子继承实现的接口都有哪些类型字段的解析字段对应的类型方法的解析方法的参数、返回值、关联了哪些类型接口方法的解析接口上的类型
5、初始化
最后一个步骤经过这个步骤后类信息完全进入了jvm内存直到它被垃圾回收器回收。
前面几个阶段都是虚拟机来搞定的。我们也干涉不了从代码上只能遵从它的语法要求。
而这个阶段是赋值才是我们应用程序中编写的有主导权的地方
在准备阶段jvm已经初始化了对应的内存空间final也有了自己的值。但是其他类变量是在这里赋值完成的。
也就是我们说的
public static int a 123;
注意
1类变量与实例变量的区分
注意一件事情
这里所说的初始化是一个class类加载到内存的过程所谓的初始化值得是类里定义的类变量。也就是静态变量。
这个初始化要和new一个类区分开来。new的是实例变量是在执行阶段才创建的。 2实例变量创建的过程
当我们在方法里写了一段代码执行过程中要new一个类的时候会发生以下事情
在方法区中找到对应类型的类信息在当前方法栈帧的本地变量表中放置一个reference指针在堆中开辟一块空间放这个对象的实例将指针指向堆里对象的地址完工