初探JVM

敏捷侠
• 阅读 793

众所周知,jvm是java里面必不可少的一个环节,虽然很多时候你意识不到它的存在,可它又无时无刻的影响着你的程序。

作为一名刚开始工作的小白,也正在学(恶)习(补)这方面的知识(无奈,因为大学期间太懒)。也希望能以简单易懂的方式把jvm的一些知识点表达出来,也当做是为自己理理思路(其实就是把有道云笔记上的东西搬上来而已)。

因水平有限,在撰写的过程中难免会出现思考不够全面或出现某些错误,欢迎各位大佬共同交流,指点迷津

程序是如何跑起来的

其实回想以下,我们的程序经过编译打包后都是以jar包或以war包的形式运行。

那么为什么它们能跑起来呢??周所周知,java程序要通过JVM才能跑起来,并可实现跨平台运行的特性。

所以无论是jar包还是war包,都是要通过jvm启动java程序才能运行起来。

初探JVM

那么,我们知道,一个jar包里面可能有很多个类,很多个.class文件。这时候,就需要jvm采用类加载器把对应的类加载到jvm中了。然后jvm会根据指定的入口,main()方法开始执行相应的代码。

初探JVM

类是如何加载的

那么可能这时候很多人就有疑问了。类是什么时候被加载的呢? 类的加载过程是怎样的呢?

那么,什么时候会去加载一个类呢?? 很简单,当你需要使用那个类的时候。

譬如Hello对象里面的main方法,new了一个Hi()对象。并使用了Hi这个对象。这个时候过程就是这样的了。

初探JVM

其实类加载的实际过程是十分复杂的,不过一般来说,类加载过程大约分为是:

graph LR
加载-->验证
验证-->准备
准备-->解析
解析-->初始化
初始化-->使用
使用-->卸载

从另一种角度上看,类加载基本就分为三个阶段

graph LR
验证-->准备
准备-->初始化
  • 验证: 简单来说就是看看你的.class文件是否符合规范,不符合规范的东西怎么可以交给jvm来跑呢?万一炸了咋办。
  • 准备: 这个时候,那个.class文件符合规范了,那就肯定要在内存里面给它腾出位置啊,不然它怎么进来对吧。所以准备阶段其实就是给类分配一定的内存空间,并给它的变量给默认值的过程。
  • 解析: 据网上资料是虚拟机将常量池内的符号引用替换为直接引用的过程。比较复杂。
  • 初始化: 就是给变量赋值的过程。

初探JVM

类加载器

那么其实类加载是很复杂的过程,所以肯定会有必要的工具来帮助它完成这个过程。它就是类加载器了

那么类加载器其实就分为几个:

  • 启动类加载器: 负责加载java安装目录下的lib类
  • 扩展类加载器:libext下的类
  • 应用类加载器: ClassPath下的类,大致可以认为是你自己写的代码
  • 用户自定义加载器: 根据你自己的需求加载你的类。

双亲委派模型: 当要加载器需要加载一个类的时候,它首先不会自己加载,而是去问抛给上一层的加载器加载,一层层往上抛。若顶层无法加载则会一层层抛回去,直至自己加载。

这样做的好处是能够防止重复加载。

初探JVM

这里可以引出一下为什么Tomcat会破坏双亲委派模型呢?

很显然,tomcat是什么,tomcat也是一个java应用程序,它也是个JVM。

那么当我们把程序部署在tomcat的时候做的是什么,无非就是把程序达成war包,然后将那个war包扔进tomcat对应的路径里面运行。

那么作为一个容器,它面临的问题是很多的。譬如可能要在里面部署多个应用程序,应用程序之间的相互依赖版本不一样,也要支持程序热部署等等。

所以,tomcat身为一个容器,既要保证不同应用程序之间的相互隔离,也要让相同版本的类库可以共享。还要将容器本身的应用程序和用户部署的应用程序隔离开来等等。。。

那么要做到这些,tomcat是怎样设计自己的类加载机制的呢???

首先Tomcat自定义了common,Catalina,Shared等类加载器。这些都用来加载tomcat自身的核心类库。

  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

那么我们都知道Tomcat里面有个webapps目录,里面放的都是我们的应用程序。Tomcat让每个Webapps下的应用程序都有一个对应的webapp类加载器,负责加载对应的web应用程序。这样就可做到相互隔离了。

至于jsp的热更新,jsp其实就是.class文件。如果jsp被修改了,但因为类名什么都是一样的,类加载器就会重新读取已经加载好的,这样是不会自动更新的。那热更新其实就是有个专门的jap加载器,jsp被修改后,就卸载掉相应的jsp类加载器,重新创建类加载器,重新加载jsp文件。

那么我们可以根据现有知识猜想下Tomcat下应用程序的类加载情况在不破坏双亲委派模型的时候是怎样的。

初探JVM

这里相信大家都能看出来问题所在了。

那么实际上tomcat下的类加载流程是这样的:

初探JVM

内存模型

那么我们知道,在java程序运行的时候,肯定是需要内存的对吧。

那所以jvm在运行的时候,必需要使用不同的内存区域,以便于存储不同类型的数据,从而让代码跑起来。

我们想想,一个java类被jvm类加载器加载之后,是不是必须要被存起来?? 否则以后怎么找得着这个被加载的类呢??

那么我们再想想,一个类里面是不是有变量,有方法,还有根据这个类创建出来的对象。这些无一例外也应该被存起来,存到内存中。

那么我们大概可以粗略的猜测,也许jvm的内存区域就是这样的。

初探JVM

其实,存放类信息的区域,被称为方法区
里面还有运行时常量池。方法区也被叫为永久代。不过在jdk1.8之后,就将其替换为元空间了

为什么要将永久代替换为元空间的其中一个原因是方法区使用的是直接内存,只受本即可用内存限制,而永久代有一块空间是jvm本身设置的内存大小,无法进行调整。因此很容易出OOM,而元空间不会。

还有很多原因感兴趣的可以自行网上查阅......

那存放对象的地方,被称为。垃圾回收其实主要就是回收一个个对象,所以垃圾回收最主要集中的地方就是堆内存了。

那么大概就是这个样子。

初探JVM

那存放方法的地方呢???

不妨试想下,一个方法可能被多个线程执行对吧。那要做到线程安全,不同线程肯定是不能共用同一块内存的。所以每个线程都应该有自己独立的内存区域。

我们知道,方法是以栈的形式执行,在debugger的时候我们就可以看到一个方法栈里面肯定要存执行方法所需要的东西,譬如方法里面的局部变量啊,对象引用地址啊,方法出口信息啊等等之类相关的东西对吧。

那么在jvm里面这种地方被分为本地方法栈虚拟机栈

虚拟机栈就是存放平时我们写的java代码的方法。而本地方法栈测试存放Native方法。

那在方法执行的时候,其实是用字节码执行引擎执行的是一个个字节码文件。

那字节码文件长啥样呢???反编译一下我们就可以看到,大概就是这个样得。

初探JVM

那么在方法执行得时候,肯定要有个东西来记录执行到哪一行代码对吧。这个东西就叫程序计数器

所以,总的来说,JVM的内存模型,大概就是这个样子了。

初探JVM

其实也就大概分成线程共享和线程私有这两大块。

线程共享的有堆,有元空间。线程私有的有程序计数器,虚拟机栈,本地方法栈。

当然,我这里只是简单地抛砖引玉的大概说下每个内存区域分别是怎样,存放什么东西的。

但实际情况肯定是比这复杂的,感兴趣的读者也可以自行了解下每个区域能具体存放什么,会发生什么异常,有什么jvm参数可以影响它等等。

点赞
收藏
评论区
推荐文章
菜鸟阿都 菜鸟阿都
4年前
玩转python爬虫
    近几年来,python的热度一直特别火!大学期间,也进行了一番深入学习,毕业后也曾试图把python作为自己的职业方向,虽然没有如愿成为一名python工程师,但掌握了python,也让我现如今的工作开展和职业发展更加得心应手。这篇文章主要与大家分享一下自己在python爬虫方面的收获与见解。   
lucien-ma lucien-ma
4年前
Java里面的十万个为什么
Java里面的十万个为什么1.不是说JVM是运行Java程序的虚拟机吗?那JRE和JVM的关系是怎么样的呢?简单地说,JRE包含JVM。JVM是运行Java程序的核心虚拟机,而运行Java程序不仅需要核心虚拟机,还需要其他的类加载器,字节码校验器以及大量的基础类库。JRE除包含JVM之外,还包含运行Java程序的其
浪人 浪人
4年前
Java后端开发三年的程序员竟然还被JVM难住!果然JVM面试是有套路的!
前言JVM是面试中必问的部分,本文通过思维导图以面向面试的角度整理JVM中不可不知的知识。先上图:image1、JVM基本概念1.1、JVM是什么JVM的全称是「JavaVirtualMachine」,也就是我们耳熟能详的Java虚拟机。JVM具备着计算机的基本运算方式,它主要负责把Java程序生成的
风斗 风斗
4年前
JMM 与 JVM的区别?很多人不清楚 ,一文带你了解得透透的
本篇文章我们主要分析一个大厂面试题:不要搞混JMM与JVM。在面试的时候,有一个问题经常被问到,那就是Java的内存模型,它已经成为了面试中的标配,是非常具有原理性的一个知识点。但是,有不少人把它和JVM的内存布局搞混了,以至于答非所问。这个现象在一些工作多年的程序员中非常普遍,主要是因为JMM与多线程有关,而且相对于底层而言,很多人平常的
Stella981 Stella981
3年前
Scala快速入门系列
写在前面的话因为Spark是由Scala开发的,所以在开发Spark应用程序之前要对Scala语言学习。虽然Spark也支持Java、Python语言,但是作为一名Java程序猿,还是决定要学习Scala哈。Scala是运行在JVM上一门语言。开发效率非常高、语法丰富简洁,三两行Scala代码能搞定Java要写的一大坨代码。
Stella981 Stella981
3年前
JVM(一)史上最佳入门指南
提到Java虚拟机(JVM),可能大部分人的第一印象是“难”,但当让我们真正走入“JVM世界”的时候,会发现其实问题并不像我们想象中的那么复杂。唯一真正令我们恐惧的,其实是恐惧本身。而作为整个JVM系列的首篇,本文将带你解除刚开始学习JVM时的种种疑惑。比如:什么是JVM?为什么学习JVM?怎么有效的学习JVM?带着以上的这些问题,让我们一起走入JVM的世界
Stella981 Stella981
3年前
JVM知识(一):基础原理
学过java知识和技术人,都应该听说过jvm,jvm一直是java知识里面晋级阶段的重要部分,如果想要在java技术领域更深入一步,jvm是必须需要明白的知识点。本篇来讲解jvm的基础原理,先来熟悉一下大致的流程:JVM运行流程:  我们都知道java一直宣传的口号:一次编译,到处运行。也是它的跨平台性。这点的具体实现如下:!(h
Stella981 Stella981
3年前
Gradle之介绍
Gradle是基于JVM构建工具的新一代版本。它从现有的构建工具如Ant和Maven中学到了很多东西,并且把它们的最优思想提升到更高层次。遵循基于约定的构建方式,Gradle可以用一种声明式的方式为你的问题领域建模,它使用一种强大的且具有表达性的基于Groovy的领域特定语言(DSL),而不是XML,因为Gradle是基于JVM的,它允许你使用自己最喜欢的J
Wesley13 Wesley13
3年前
Java 内存泄漏
  Java的一个重要优点就是通过垃圾收集器(GarbageCollection,GC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,因为Java也存在内存泄露,但它的表现与C不同。  随着越来越多的服
聊聊JDK1.0到JDK20的那些事儿 | 京东云技术团队
最近小组在开展读书角活动,我们小组选的是《深入理解JVM虚拟机》,相信这本书对于各位程序猿们都不陌生,我也是之前在学校准备面试期间大致读过一遍,emm时隔多日,对里面的知识也就模糊了。这次开始的时候从前面的JDK发展史和JVM虚拟机家族着手,之前都是粗略读过,这次通过查阅相关资料并收集在每一个JDK版本演化期间所发生的的一些趣闻,发现还是比较有意思的,以下是关于有关JDK发展史的总结分享。
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了