• 0

  • 528

初探JVM

黑猫

我不是黑客

1星期前

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

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

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

程序是如何跑起来的

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

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

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

image

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

image

类是如何加载的

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

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

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

image

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

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

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

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

类加载器

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

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

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

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

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

image

这里可以引出一下为什么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下应用程序的类加载情况在不破坏双亲委派模型的时候是怎样的。

image

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

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

image

内存模型

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

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

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

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

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

image

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

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

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

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

那么大概就是这个样子。

image

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

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

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

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

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

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

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

image

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

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

image

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

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

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

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

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

信息安全

528

相关文章推荐

未登录头像

暂无评论