狂神——JVM

1、JVM的位置

在这里插入图片描述

上面4个A是Java程序,在JVM里运行,JVM实际就是个软件,和其他正常软件平级。

2、JVM体系结构

在这里插入图片描述

栈里肯定不能存在垃圾,因为如果有垃圾就堵住栈,下面的main方法就执行不了,程序就死了。栈、本地方法栈、程序计数器一定没有垃圾。

堆里可能有垃圾。

所以所说的JVM调优,其实99%就是对方法区和堆进行调优

img

3、类加载器

作用:加载Class 文件,~ new Student();

类是模板,是抽象的;对象是实现,是具体的。

在这里插入图片描述

右面部分是类加载器

类加载器的分类:

  1. 虚拟机自带的加载器
  2. 启动类(根)加载器 Bootstrap ClassLoader:负责加载jre\lib目录下的rt.jar
  3. 扩展类加载器 Extention ClassLoader:负责加载jre\lib\ext目录下的所有jar包
  4. 应用程序(系统类)加载器 Application ClassLoader:负责加载用户类路径上所指定的类库,如果应用程序中没有自定义加载器,那么此加载器就为默认加载器。

找的时候会一层层往上找。

4、双亲委派机制

作用:为了保障安全

运行一个类,先向上找APP—>EXC—>BOOT(最终执行)

双亲委派机制步骤:

1、类加载器先收到类加载的请求

2、将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器

3、启动类加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则(如果都找不到)就抛出异常,通知子加载器进行加载

4、重复步骤3,如果应用程序加载器也没有,抛出异常,ClassNotFound

向上请求,向下加载

执行过程:在运行一个类之前,

首先会在应用程序加载器(APP)中找,继续向上在扩展类加载器EXC中找,然后再向上在启动类()加载器BOOT中找。

如果在BOOT中有这个类的话,最终执行的就是根加载器中的。如果BOOT中没有的话,就会倒找往回找。
当一个Hello.class这样的文件要被加载时。
1、不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过:
如果有,那就无需再加载了;如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。
2、父类中同理也会先检查自己是否已经加载过,如果没有再往上。
注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。
3、直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

img

5、沙箱安全机制sandbox

(简单了解,过时了)

什么是沙箱?

沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏

基本组件

1、类加载器

在这里插入图片描述

类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

类装载器采用的机制是双亲委派模式

2、字节码校验器

3、存取控制器

4、安全管理器

5、安全软件包

​ java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:

  • 安全提供者
  • 消息摘要
  • 数字签名 keytools https
  • 加密
  • 鉴别

6、Native(唬住面试官!)

  • 凡是带了native 关键字的,说明java 的作用范围达不到了,会去调用底层C语言的库。

  • 凡是带了native 关键字的,会进入本地方法栈,调用本地方法接口 JNI。

  • JNI作用:扩展java的使用,融合不同的编程语言为java所用!如:C,C++

  • Java诞生的时候 C、C++ 横行,想要立足,必须要有调用C、C++的程序

  • 它在内存区域中专门开辟了一块标记区域:Native Method Stack,登记 native 方法

  • 在最终执行的时候,通过本地接口 (JNI)加载本地方法库中的方法。

  • Java程序驱动打印机,管理系统,在企业级应用中较为少见

在这里插入图片描述

图片参考至

现在调用其他接口的解决方法:http,rdbc, Socket, WebService

Native Method Stack

它的具体做法是Native Method Stack 中登记native方法,在(Execution Engine)执行引擎执行的时候加载Native Libraies。

7、PC寄存器

程序计数器:Programma Counter Register

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计

8、方法区(很重要的东西)

方法区是被所有线程共享的,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息多保存在该区域,在区域属于共享区间。

静态变量static、常量final、类信息class(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。

static, final , Class模版, 常量池

在这里插入图片描述

原文链接:https://blog.csdn.net/qq_43605227/article/details/119066790

(面试题)一个对象在类加载的时候是什么样子?画出来,实际就是考JVM。

9、栈!(重要)

1、栈:数据结构

程序 = 数据结构+算法 : 持续学习

程序 = 框架 + 业务逻辑 : 吃饭

栈: 先进后出、后进先出 : 桶

队列:先进先出(FIFO:First Input First Output)

为什么main()先执行,最后结束?main首先进入栈,一层层调用,最后都执行完,main也出栈,整个程序结束。

栈:栈内存,主管程序的运行,生命周期和线程同步;

线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题

一旦线程结束,栈就是Over!

都有什么内容在栈里:8大基础类型+对象引用+实例方法

栈运行原理:栈桢

栈满了:StackOverflowError

在这里插入图片描述

栈+堆+方法区:交互关系

class文件是在方法区里,但是是在堆里面对方法区的class信息进行了一个封装创建了一个对象

在这里插入图片描述

新new一个类的过程,往栈里丢一个引用的名字,在堆里实例一个类,最后引用指向这个类

作业:画出一个对象实例化的过程在内存中

10、三种JVM

  • Sun公司HotSpot Java HotSpot(TM)64-Bit Server VM (build 25.181-b13, mixed mode)
  • BEA JRockit:最快的JVM,适合财务前端办公、军事指挥和电信网络
  • IBM J9 VM

我们现在学的是:HotSpot

11、堆

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

  • 类加载器读取了类文件后,一般会把什么东西放到堆中?类,方法,常量,变量,保存我们所有引用类型的真实对象。

  • 堆内存中还要细分三个区域:

    • 新生区(伊甸园区)Young/New
    • 养老区 old
    • 永久区 Perm
  • GC:Garbage recycling
    轻GC:轻量级垃圾回收,主要是在新生区
    重GC(Full GC):重量级垃圾回收,主要是养老区,重GC就说明内存都要爆了

在这里插入图片描述

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

幸存区就相当于是伊甸园区和养老区的过度

假设内存满了,OOM,堆内存不够! Java.lang.OutOfMemoryError:java heap space 此时程序就很严重了

在JDK8以后,永久存储区改了个名字(元空间);

新生区、老年区

新生区:

  • 类:诞生和成长的地方,甚至死亡
  • 伊甸园:所有的对象都是在伊甸园区new出来的
  • 幸存者区(0,1)

在这里插入图片描述

真理:经过研究,99%的对象都是临时对象!

永久区

这个区域常驻内存的。用来存放JDK自身携带的Class对象、Interface、元数据,也就是存储的是Java运行时的一些环境或类信息

这个区域不存在垃圾回收! 关闭JVM虚拟就会释放这个区域的内存.

  • 出现OOM可能是:

    • 一个启动类,加载了大量的第三方jar包。
    • Tomcat部署了太多的应用,大量动态生成的反射类。不断的被加载。直到内存满,就会出现OOM;
  • JDk1.6之前:永久代,常量池是在方法区

  • JDK1.7 :永久代,但是慢慢的退化了,去永久代,常量池在堆中

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

结构图:

img

图源这里:方法区中的最小的框是常量池

方法区(叫非堆)属于堆里,能被所有线程共享,对象在新生区,去方法区里拿值

也有人这么认为:

在这里插入图片描述

元空间又被称为非堆,但他也是堆,也有人认为右侧区域是堆空间,只是个人理解不同。

元空间:逻辑上存在,物理上不存在(因为存储在本地磁盘内)而不算在JVM虚拟机内存中,也就是并没有占堆内存。

这里视频和弹幕有争议,弹幕:

1-首先在JDK1.8时候就已经删除永久代(永久区),搞了个新的词叫元空间

2-元空间是独立出来的,从逻辑,从硬件存储上彻底和堆(伊甸园【或者叫新生区】、老年代【老年区】)

3-在1.7以前,持久代(又叫方法区)在物理存储上是和堆一起的(堆物理层面包含了,新生、老年、持久)。但是我们在逻辑上要把方法区和堆分开。方便区分堆和方法区这两个概念。


public class Demo02 {
    public static void main(String[] args) {
//        返回虚拟机试图使用的最大内存
        long max = Runtime.getRuntime().maxMemory();  // 字节  1024 * 1024
//        返回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
    }

    /*
    OOM:
        1、尝试扩大堆内存看结果
        2、分析内存,看一下哪个地方出了问题(专业工具)
        -Xms1024m -Xmx1024m -XX:+PrintGCDetails
         新生代     老年代     total
        305664K + 699392K = 1,005,056K = 985.5M
    * */
}


在这里插入图片描述

img

可以通过调整这个参数(Edit Configuration—VM options)控制Java虚拟机初始化内存的总内存大小。

在这里插入图片描述

Xms字面意思是最小内存,这里可以理解成初始化时的内存分配,Xmx也就是允许分配的最大内存空间。

在一个项目中,突然出现了OOM故障,那么该如何排除,研究为什么出错

  • 能够看到代码第几行错误:内存快照分析工具,MAT(Eclipse用的)、Jprofiler
  • Dubug,一行行分析代码!

Jprofiler使用

MAT、Jprofiler作用

  • 分析Dump内存文件,快速定位内存泄露;
  • 获得堆中的数据
  • 获得大的对象

Jprofiler使用

1、在idea中下载Jprofiler插件

2、百度搜索官网下载Jprofiler客户端,安装路径要求:没有中文没有空格

3、百度破解完之后,在IDEA的Setting->Tools找到Jprofilers,然后绑定安装目录bin下的.exe文件

4、在idea中VM参数中写参数 -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError (假如堆内存heap出现了OOM则dump出这个异常)
5、运行程序后在jprofile客户端中打开(dump出的文件应该在src目录下)找到错误 告诉哪个位置报错

6、打开该文件所在的位置,然后一直上一层上一层,出现这个为止

img

7、直接打开,先看看是哪个占用堆内存最多。然后再去看线程,点开就可以看到哪个线程哪里出现问题了

命令参数详解

  • -Xms设置初始化内存分配大小,默认1/64
  • -Xmx设置最大分配内存,默以1/4
  • -XX: +PrintGCDetails // 打印GC垃圾回收信息
  • -XX: +HeapDumpOnOutOfMemoryError //OOM DUMP

12、GC:垃圾回收

只能自动进行垃圾回收,手动不行

在这里插入图片描述

JVM在进行GC时,并不是对这三个区域统一回收。大部分时候,回收都是新生代

  • 新生代
  • 幸存区(from、to):幸存区0区和幸存区1区两者是会交替的,from和to的关系会交替变化
  • 老年区

GC两种类:轻GC(普通的GC)、重GC(全局GC)

轻GC:只针对新生区,偶尔幸存区

重GC:所有东西都要清一遍

img

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

13、GC常用算法

引用计数法,复制算法,标记清除法,标记压缩

img

引用计数法:

在这里插入图片描述

比较落后

复制算法:

谁空谁是to区

在这里插入图片描述

  • 复制算法的步骤:

目前是eden中绿色部分和幸存区from的绿色部分有活的对象。

下一步操作就是将Eden中的活对象和from区的活对象放到To区中,然后From区变成To区(因为To区永远是空的)

第一次GC后,Eden区和to区空了,经过15次垃圾回收后依然存活下来的对象就会去养老区

  • 优缺点

    • 好处:没有内存的碎片
    • 坏处:浪费内存空间(一个幸存区的空间永远是空:to)。假设对象100%存活(极端情况)
  • 复制算法最佳使用场景:对象存活度较低的时候:新生区

img

标记清除:

在这里插入图片描述

  • 优点:不需要额外的空间!
  • 缺点:需要两次扫描,严重浪费时间,会产生内存碎片。

标记压缩:

  • 改进:

对标记清除算法进行优化,就是将活着的算法移到一起,弥补它会产生内存碎片的问题

在这里插入图片描述

  • 再改进:先多标记清除几次之后,再压缩1次。

14.GC算法总结

从两个维度:内存效率、内存整齐度、内存利用率进行比较

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

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

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

没有最好的算法,只有最合适的算法,所以GC也被称为分代收集算法

年轻代(或者伊甸园或者性能区):

  • 存活率较低,所以在这个区域更适合用复制算法

老年代

  • 区域大,存活率较高。所以更适合用标记清除(如果内存碎片不是太多就可以使用)+ 标记压缩混合(内存碎片达到一定量级的时候就压缩)实现

15、JMM

什么是JMM

(Java Memory Model) java内存模型

JMM是干什么的?

作用:缓冲一致性协议,用于定义数据读写的规则

(所以我们要做的就是找到这个规则并遵守)

在这里插入图片描述

JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)

弹幕:说白了就是cpu缓存跟主存的数据同步

可以把主存理解为堆、方法区;工作内存理解为java栈、程序计数PC等线程私有的内存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值