JVM知识点梳理

1. JVM与其运行机制

        JVM是java虚拟机,它是运行在操作系统之上的。我们在编译器中编写的代码文件是.java文件(原代码文件),原代码文件通过编译器编译成.Class文件。而JVM就是用来编译.class文件(字节码文件),将其编译成机器码,使其能在不同的操作系统上运行。

举个生动的例子:

        假设我们是商人,现在我们要与其他各国的商人做生意,但是语言不通可做不成生意,交流都成问题啊。于是这个时候我们就需要一个重要的角色——翻译人员。我们要与A国的商人交易,那么就需要会A国语言的翻译来帮我们沟通;我们要与B国的商人交易,那么就需要会B国语言的翻译来帮我们沟通,C国、D国同理。只要有了翻译,那我们就可以随便跨国做生意,赚大钱! 那么在这里,我们自己扮演的商人这个角色就像是我们编写的代码。那些A、B、C、D国就是不同的操作系统。想要我们编写的java代码可以在任意的操作系统上运行,就需要一个“翻译”,JVM就是这个翻译。

JVM由类加载子系统、运行时数据区、执行引擎和本地接口组成。

  • 类加载子系统用于将.Class文件加载到JVM中。

  • 运行时数据区则是用来储存运行时产生的数据。

  • 执行引擎包括一个即时编译器和垃圾回收器。编译器用于将字节码编译成机器码,垃圾回收器则是用来回收不再需要的对象。

  • 本地接口通过调用本地方法库完成具体的操作。


2. 类加载子系统

        JVM中类的加载分为五个阶段:加载、验证、准备、解析、初始化。类的初始化完成后就可以使用这个类了。加载阶段主要是读取.Class文件并创建Class对象。准备阶段是为类变量分配内存空间并设置初始值。

        类的加载通过类加载器来实现,JVM提供了三种类加载器:启动类加载器、扩展类加载器和应用程序类加载器。当然我们还可以通过继承ClassLoader来实现自定义类加载器。包括自定义类加载器在内一共四种类加载器他们是有层级划分的。由最高的启动类加载器到扩展类加载器再到应用程序类加载器最后自定义。在双亲委派机制中会有所体现。

JVM中通过 双亲委派机制 来加载类:

        其实就是指当我们自定义加载器接收到一个加载类的指令后并不会立刻自己加载这个类,而是会向上委派给自己的父类去加载;而父类加载器也是这个球样子,它还会向上委派给爷爷辈类加载器去加载。然后最终都会被委派到祖宗辈——启动类加载器。由祖宗类加载器来加载该类,如果加载不出来,才会逐级向下委派子类加载器去加载,直到该类被加载出来为止。如果到头都加载不出来,那JVM就会抛出我们所知的ClassNotFound异常。

        双亲委派机制的目的就是保障被加载类的唯一性和安全性

        有些情况下类不会被初始化,例如:当子类引用父类的静态字段时,子类不会被初始化,父类会。等等...


3. 运行时数据区

运行时数据区包括了虚拟机栈、虚拟机堆、方法区、本地方法区和程序计数器。

1.虚拟机栈

        虚拟机栈描述了java中方法的执行过程,方法的执行和结束对应了栈中的入栈和出栈。每有一个方法执行,JVM就会为其创建一个栈帧并且入栈,方法结束则出栈。栈中还存放着运行时产生的数据,包括八大基本数据类型以及对象的引用。虚拟机栈属于线程私有,生命周期与线程相同。

2.虚拟机堆

        也叫运行时内存,虚拟机堆存放着在运行中创建的对象和产生的数据。属于线程共享,随虚拟机的启动而创建,关闭而销毁。堆内存通常还被细分划为:新生代、老年代和永久代三个区域。其中新生代默认占1/3的区域,老年代占2/3。至于永久代,在java8中永久代被元空间所取代,而元空间不在虚拟机中,而是使用的本地内存。JVM新创建的对象都会存储在新生代中;而那些生命周期比较长的就存放在老年代中;永久代就是永久保存的区域了,通常存放着Class和元数据。类被加载时Class就被放入了永久代。其中新生代又细分为:Eden区、SurvivorTo区和SurvivorFrom区。(首先新创建的对象会储存在Eden区,当Eden区内存空间不足时就会触发一次GC,对Eden和From进行垃圾回收,然后在SurvivorTo区会存放GC后的幸存者,此时Eden区的内存空间得到释放。然后将To区和From区互换,换前的To区将成为下次被GC回收的From区)

3.方法区

        线程共享,方法区中存放着常量池和即时编译器(执行引擎)编译的机器码以及静态变量等。

4.本地方法区

        线程私有,本地接口通过本地方法库为本地方法区提供服务。

5.程序计数器

        线程私有,用来记录线程执行的字节码的行数,说白了就是记录线程执行的步骤。


4. 垃圾回收机制(GC)

1. 如何分辨谁是垃圾?谁需要被回收?

  • 引用计数法:因为在java中想要调用对象必须为对象设置引用,那么就可以通过对象的引用数量判断是否为垃圾,如果引用数为0那么就判定为垃圾,需要回收。

  • 可达性分析:定义一些GC Roots对象 ,如果GCRoots与对象之间没有可达路径则对其进行标记,两次标记后会判定为垃圾。

2. 常用垃圾回收算法

  • 标记清除算法:首先标记确定需要清除的对象,然后再对被标记的对象进行清除,释放内存空间。但缺点就是被清除的对象零零碎碎的分布在整个内存空间内,被清除后就可能会出现大量的内存碎片,断断续续的,没有一片连续可用的内存空间。

  • 复制算法:将内存划分为两个区域A和B,对象都被储存在A区,当内存满时清理A区域的垃圾,然后将剩余的对象整理后复制到B区域。这样经过整理后就避免了内存碎片化。但是缺点也同样明显,就是浪费了一块内存区域。

  • 标记整理算法:结合了标记清除和复制算法,内存问题得到解决,但是环节太多耗费时间。(也可以进行优化:如多次标记清除后进行一次整理)

  • 分代收集算法:由于各个算法都有各自的优缺点,针对不同的对象,可以有不同的最优解,将这个算法称为分代收集算法。即针对新生代使用复制算法(GC后存活少量对象,占用内存少),老年代采用标记整理算法(GC回收的对象较少)。


5. java中的4种引用类型

  • 强引用 (最常见的直接把对象赋予一个引用变量)

  • 弱引用

  • 软引用

  • 虚引用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值