目录
一、概念
基本概念
JVM是可运行的Java代码的假想计算机,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收,堆和一个存储方法域。JVM是运行在操作系统之上的它与硬件没有直接交互。
运行过程
JVM很复杂实际上也确实很复杂,但是作为我们来说短期内只需要搞清楚一个就完了:
他是一个**“翻译器”**
也就是:
Java源文件经过编译器变成可执行的字节码文件,字节码文件经过JVM变成机器码文件就可以执行了,如下流程
① Java源文件 —->编译器 —->字节码文件
②字节码文件 —->JVM>JVM >JVM—->机器码
每一种平台的解释器是不一样的,但是实现的虚拟机制是一样的,这就是Java为什么可以跨平台的原因,当一个程序从开始运行,这时候虚拟机就开始实例化了,多个程序启动,就存在多个虚拟机实例,程序退出或者关闭,虚拟机实例消亡,多个虚拟机实例之间数据不能共享。
线程
这里说的线程是值程序执行过程中的一个线程实体,JVM允许一个应用并发执行多个线程。
Hostpot JVM中的Java线程与原生操作系统贤臣有直接的映射关系。
当线程本地存储、缓冲区分配、同步对象、栈、程序计数器等准备好了以后,就会创建一个操作系统的原生线程。Java线程结束,原生线程随之被回收。操作系统负责调度所有线程,并把它们分配到任何可以用的cpu上。当原生线程初始化完毕,就会调用Java线程的run()方法,当线程结束时,会释放原生线程和Java线程的所有资源。
Hotspot JVM 后台运行的系统线程主要有下面几个:
虚拟机线程(VM thread)
这个线程等待 JVM 到达安全点操作出现。这些操作必须要在独立的线程里执行,因为当堆修改无法进行时,线程都需要 JVM 位于安全点。这些操作的类型有:stop-the-world 垃圾回收、线程栈 dump、线程暂停、线程偏向锁(biased locking)解除。
周期性任务线程
这线程负责定时器事件(也就是中断),用来调度周期性操作的执行。
GC 线程
这些线程支持 JVM 中不同的垃圾回收活动。
编译器线程
这些线程在运行时将字节码动态编译成本地平台相关的机器码。
信号分发线程
这个线程接收发送到 JVM 的信号并调用适当的 JVM 方法处理。
这些是简单的JVM基础概念,下面我将进行一些细致的讲解,可能比较白话。
二、JVM具体详细解释
JVM:其概念首先是Java虚拟机,然后其运行的是字节码,这正是跨平台的关键。
Java程序执行过程:
File.java(源文件) – 编译:javac --> File.class(字节码文件) –
– JVM(跨平台) -->机器执行的二进制
其中,JVM在不同机器上的JVM不同,但是都可以翻译相同的字节码,将其翻译成为不同机器可以识别的机器码,像Linux、Windows、Mac都有对应的机器码。在电脑上安装对应的JRE就可以运行同一个File.class。
此处有一个知识点(别好奇为啥我这个知识点是在这理了解到的): ==与equals的区别:
”==“使用的时候如果是基本类型,像int、float、double比较的是值是否相等,但是如果是引用类型,像String、Integer以及自己定义的user类型的时候比较的是引用是否相同。
equals本质其实也是”==“,但是String和Integer等重写equals方法,把它变成了值比较。
==对基本类型是值比较,对于引用类型来说是比较引用,而equals默认情况下是引用比较,只是很多类重写了equals方法,比如String、Integer等把它变成了值比较,所以一般情况下equals方法,比如String、Integer等把它变成了值比较,所以一般情况下equals比较的是值是否相等。
其实这个地方还涉及一个重写equals,由于篇幅问题加上实际开发中原本的比较暂时也够,所以有机会专门写一个重写equals,暂时先不过多讨论,JVM还有很多值得说的。
JVM内存结构:
本地方法栈、Java虚拟机栈、程序计数器、堆、元空间、直接内存。
字符串常量在字符串常量池(JDK8以后再堆上)、类文件在元空间。
上述其实也有,但是可能不好理解,所以我自己画了一个简化版本。
对象存在堆中(栈上的变量指向堆的对象),程序=算法+数据结构,软件就是程序,也就是所谓的数据结构和算法(ps:说的什么东西,我要说什么?上图)。
native:本地方法(需要用底层内库调用)
类的模板储存在元空间。
ps:不知道我解释的清楚堆不,斗胆一试。
首先我们画一个堆的样子,大概的了解一下堆:
解释一下图,首先堆分为新生代和老年代,分别占比1/3和2/3,新生代里面有Eden区、From区和To区,空间占比为8:1:1。暂时只需要了解这么多。
堆的分代策略:
gc root(可达性判断:判断对象是否仍被引用)之后对象被引用的进入from区,此时age=1,此时程序会再次进行gc root也可以称为回收,如果from区仍然有对象,age++,to区与from区进行数据交换,第三回收age++,to区与from区交换 。。。如果此时有一个对象在进行15次回收之后,仍然被引用,没有被回收,则会进入老年代,继续执行。
当然老年代也会满,也会垃圾回收,产生full gc,同时会出现STW,即Stop The World(不翻译,嘿嘿)。
为什么要分代策略:其实就只是一个简单的目的:让更少的对象进入老年代,一个排查筛选的过程。我们知道空间是有限的,虽然在现在这个时代我们的感觉不是很明显,但是在以前空间是很少的我们需要节约,而如果空间一旦超了就会发生系统崩溃等不可控制的后果。为了避免这个,我们在硬件设计的时候选择了建立临界点的方式,比如空间有10G,我们起码留1/3空间作为临界点,达到临界点系统无法再执行,但是可以保证数据安全。如图所示:
为什么Java要性能调优:目的,在有限的空间做无限的事情
三、垃圾收集算法:
ps:此处我用自己的话解释一下,官方点的,自行百度一下~~
标记清除: 先扫描堆里的东西,逐个判断哪个是垃圾,然后进行标记,最后清除(坏处:产生碎片)
复制算法: 先扫描标记垃圾,将空间分为两个区域,将当前存活对象移动到另一个区域,过程中会重新排序,使紧凑。(坏处:消耗空间大)
标记整理: 同样是先标记清除垃圾,最后进行空间整理(坏处:效率低下)
分代收集算法: 老年代用标记-清除,标记整理,年轻代用复制算法。
GC root:可达性判断,判断对象是否被引用。
CMS:Corcurrent(多线程) Mark Sweep(标记清除)
四、总结
以上就是JVM的相关知识,希望看到的兄弟姐妹多多纠正错误和补充,如果觉得不错,点下赞。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。