【狂神说】JVM快速入门学习笔记

JVM快速入门

一、常见面试题

  • 请你谈谈你对JVM的理解?java8虚拟机和之前的变化更新?
  • 什么OOM,什么是栈溢出StackOverFlowError?怎么分析?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取,怎么分析Dump文件?知道吗?
  • 谈谈JVM中,类加载器你的认识?

二、知识点

  1. JVM的位置
  2. JVM的体系结构
  3. 类加载器
  4. 双亲委派机制
  5. 沙箱委派机制
  6. Native
  7. PC寄存器
  8. 方法区
  9. 三种JVM
  10. 新生区、老年区
  11. 永久区
  12. 堆内存调优
  13. GC
    • 常用算法
  14. JMM
  15. 总结

三、学习方式

  • 百度
  • 思维导图

四、详解

4.1、JVM的位置

在这里插入图片描述

4.2、JVM体系结构

在这里插入图片描述

在这里插入图片描述

  • “对象的引用”在栈里面
  • 真正对象的“数据”在堆里面

4.3、类加载器

1、引入

类是“模板”,对象是具体的

public class Car {
    public static void main(String[] args) {
        //类是模板,对象是具体的
        Class<Car> carClass = Car.class;
    }
}

在这里插入图片描述

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x7MgbOA8-1650153661657)(笔记图片/类加载器.png)]

2、加载器的分类
  • 虚拟机自带的加载器
  • 启动类(根)加载器
  • 扩展类加载器
  • 应用程序(系统类)加载器

双亲委派机制:其实就是每个类加载器都很懒 都想让父类(只是字段并非真正的父类)来加载,父类加载不了的才自己加载(懒鬼)(百度搜索看看理解)

4.4、沙箱安全机制

1、什么是沙箱?

​ Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

​ 所有的Java程序运行都可以指定沙箱,可以定制安全策略。java中的安全模型。

2、沙箱安全机制发展
  • 对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。如下图所示 JDK1.0安全模型
    在这里插入图片描述

  • 但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的 Java1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示 JDK1.1安全模型
    在这里插入图片描述

  • 在 Java1.2 版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示 JDK1.2安全模型
    在这里插入图片描述

  • 当前最新的安全机制实现,则引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示 最新的安全模型(jdk 1.6)
    在这里插入图片描述

4.5、native关键字

凡是被native修饰的,说明java的作用范围达不到了,会去调用c语言的库!

以“start0();”方法为例
  • 调用被native关键字修饰的方法,会进入本地方法栈
  • 然后会调用本地方法的本地方法接口(JNI)
  • JNI的作用:扩展java的使用,融合不同的编程语言为java所用!最初是c/c++
  • java诞生的使用,c/c++很流行,想要立足,就必须要用c/c++的程序
  • 因此,它在内存区域中专门开辟了一块标记区域Native Method Stack,来登记native方法
  • 在最终执行的时候,通过JNI(本地方法接口)来加载本地方法库中的方法
  • 平常用不到这类的方法,该方法多操控硬件资源

4.6、PC寄存器(程序计数器)

程序计数器:Program Counter Register(PC计数器或者指令计数器更贴切)

  • 线程私有,指向方法区中字节码(用来存储指向一条指令的地址,也就是即将要执行的指令代码)。是一个非常小的内存空间,几乎可以忽略不计。
  • Java虚拟机的多线程是通过线程间的轮流切换、分配处理器执行时间来实现的,所以为了让线程切换后恢复到正确的执行位置,每个线程的计数器是独立的,互不影响,包括主线程。

4.7、方法区

方法区:Method Area

  • 方法区是被所有线程共享的,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义;简单的来说,所有定义的方法的信息都保存在该区域中,此区域属于共享空间
  • 静态变量、常量、类信息(构造方法,接口定义),运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关

4.8、栈

引入:

  • 程序:数据结构+算法
  • 程序:框架+业务逻辑

模型理解:

  • 栈:先进后出(“桶”)——喝多了吐
  • 队列:先进先出(“FIFO”)——吃多了拉
理解什么叫“栈溢出”?(StackOverflowError)

代码示例:

package com.my.test;
//理解什么叫栈溢出
//为什么main()方法先执行后结束
public class Test03 {
    public static void main(String[] args) {
        Test03 test03 = new Test03();

        test03.a();
    }
    public void a(){
        b();
    };
    public void b(){
        a();
    };
}

模型理解:
在这里插入图片描述

栈详解

栈:也叫栈内存;主管程序的运行,它的生命周期和线程同步;线程结束,栈内存也就释放了;对于栈来说,不存在垃圾回收问题;一旦线程结束,栈就“Over”;

栈:8大基本类型+对象引用+实例的方法

栈运行原理:栈帧,程序正在执行的方法一定在栈的顶部
在这里插入图片描述

栈和堆的关系

在这里插入图片描述

4.9、三种JVM

  • HotSpot
  • BEA jrockit
  • IBM J9 VM

4.10、堆

堆(Heap):

  • 一个JVM只有一个堆内存,堆内存的大小是可以调节的

类加载器读取了类文件之后,一般会把什么东西放在堆中?

  • 类、方法、常量、变量,保存了我们引用类型的真实对象

堆内存还细分为三个区域:

  • 新生区(伊甸园区)young/new
  • 养老区old
  • 永久区perm
    在这里插入图片描述

GC垃圾回收:主要在伊甸园区和养老区

“OOM”:内存满了,堆内存不够

JDK8以后永久存储区更名为“元空间”

新生区
  • 类:诞生和成长以及死亡的地方
  • 伊甸园区:所有的对象都是由伊甸园区new出来的
永久区

概念:

  • 这个区域常驻内存,用来存放JDK自身携带的Class对象、interface元数据、存储的是java运行时的一些环境或者类信息
  • 这个区域不存在垃圾回收;除非关闭虚拟机才会释放这个区域的内存

崩溃情况:

  • 一个启动类加载了大量的第三方jar包
  • Tomcat部署了太多的应用
  • 大量动态生成反射类,不断被加载,直到OOM

jdk1.6之前:永久代,常量池是在方法区

jdk1.7:永久代,但慢慢的退化了,“去永久代”,常量池在堆中

jdk1.8之后:无永久代,常量池在元空间

在这里插入图片描述

备注:元空间逻辑上存在,物理上不存在

堆内存调优
package com.my.test;

public class Test04 {
    public static void main(String[] args) {
        //返回虚拟机试图使用的最大内存
        long max = Runtime.getRuntime().maxMemory();
        //返回jvm的初始化内存
        long total = Runtime.getRuntime().totalMemory();

        System.out.println("max="+max+"字节\t"+(max/(double)1024/1024)+"MB");
        System.out.println("total="+total+"字节\t"+(total/(double)1024/1024)+"MB");

        /*
        * 总结:
        *   虚拟机使用的最大内存是电脑内存的约:1/4
        *   初始化的内存是电脑内存的约:1/64
        *
        * 更改虚拟机内存:
        *   -Xms1024m -Xmx1024m -XX:+PrintGCDetails
        * */
    }
}

报OOM堆内存溢出排错思路:

  • 尝试扩大堆内存,来看看结果
  • 分析内存,看哪一个地方出现了问题(专业工具)
OOM排错思路

在一个项目中,突然出现了OOM故障,那么该怎么排错?

  • 能够看到代码第几行出错:内存快照分析工具
  • Debug一行行代码进行分析

内存快照分析工具:

  • MAT(eclipse集成工具)
  • Jprofiler

内存快照分析工具的作用:

  • 分析Dump内存文件,快速定位内存泄露
  • 获得堆中的数据
  • 获得大的对象
安装Jprofiler开发工具
  • 首先在idea的setting面板来下载插件

  • 下载完成后重启idea

  • 百度搜索,官网下载

  • 下载完成后进行安装,输入产品密钥

  • 然后启动idea找到对应插件进行配置

在这里插入图片描述

4.11、GC

在这里插入图片描述

JVM在进行GC(垃圾回收)时,并不是对三个区域都进行回收;大部分都回收的是新生区

GC的分类:

  • 轻GC(普通的GC)
  • 重GC(全局的GC)

题目:

  • JVM的内存模型和分区,详细到每个区都放什么?
  • 堆里面的分区有哪些?Eden、from、to、老年区;总结一下它们的特点?
  • GC的算法有哪些?标记清除法、标记压缩法、复制算法、引用计数法;它们怎么使用的?
  • 轻GC和重GC在什么时候发生?
引用计数法

在这里插入图片描述

复制算法

在这里插入图片描述

在这里插入图片描述

好处:

  • 没有内存碎片

坏处:

  • 浪费了内存空间,多了一半空间永远是to(空),假设对象100%存活,根据复制算法需要全部复制

复制算法使用场景:对象存活度极低的情况(新生区)

标记清除算法

在这里插入图片描述

优点:不需要额外的空间

缺点:两次扫描,严重浪费时间,会产生内存碎片

标记压缩算法

对标记清除算法再优化!
在这里插入图片描述

总结

内存效率(时间复杂度):复制算法>标记清除算法>标记压缩算法

内存整齐度:复制算法=标记压缩算法>标记清除算法

内存利用率:标记清除算法=标记压缩算法>复制算法

思考题:难道没有最优的算法吗?

  • 没有最优的,只有最合适的

年轻代:

  • 存活率低(复制算法)

老年代:

  • 区域大、存活率高(标记清除+标记压缩)

推荐书籍:《深入理解JVM》

4.12、JMM(Java Memory Model)

1、什么是JMM(百度百科)

(Java Memory Model的缩写)

2、它的作用是什么?(官方介绍、博客、视频)

作用:

  • 缓存一致性协议,用于定义数据读写的规则
  • JMM定义了线程工作内存和主内存之间的关系;线程之间的共享变量存储在主内存的,每个线程都有一个私有的本地内存

怎么解决共享对象的可见性问题:volilate
在这里插入图片描述

3、它该怎么学习

总结JMM

五、知识扩展

1、什么叫java字节码文件?(.class文件)

  • java字节码文件是程序的一种低级表示
  • 将程序抽象成字节码可以保证java程序在各种设备上运行
  • java文件通过编译器编译成java字节码文件(.class文件),这个过程是java的编译过程
  • Java号称是一门“一次编译到处运行”的语言
  • java虚拟机执行的就是字节码文件(符合java虚拟机规范)

2、什么叫类加载器?

概念:

  • 类加载器是一个用来加载类文件的类
  • Java源代码通过javac编译器编译成类文件
  • 当需要使用某个类时,虚拟机将会加载它的".class"文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载
  • 然后JVM来执行类文件中的字节码来执行程序
  • 类加载器的任务就是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例

分类:

  • Bootstrap类加载器:启动类加载器
  • Extension类加载器:扩展类加载器
  • System类加载器(或者叫作Application类加载器):系统类加载器

3、类加载器的工作原理

类加载器的作用就是在运行时加载类;

java类加载器基于三个机制:

  • 委托:指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它(类加载器都很懒,都想要它的父类加载器“干活”)
  • 可见性:子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类
  • 单一性:指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类

4、类加载的过程

在这里插入图片描述

在这里插入图片描述

  • 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
  • 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证
  • 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中
  • 解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用
  • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)

4、JDK、JRE和JVM的区别和联系

  • JDK是 Java 语言的软件开发工具包(SDK)
  • 在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib
    • 在这里可以认为bin里的就是jvm
    • lib中则是jvm工作所需要的类库
    • jvm和 lib合起来就称为jre
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凉水不好喝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值