JVM探究

JVM探究

1.JVM的位置

在这里插入图片描述

2.JVM的体系结构

在这里插入图片描述
在这里插入图片描述

3.类加载器

负责加载class文件,将class文件字节码加载到内存中,并将这些内容转换成方法区中的运行时数据结构。类加载器只负责class文件的加载,至于他是否可以运行,由Excution Engine决定。

Car.class是由.java文件编译得来的.class文件,存在本地磁盘。

ClassLoader:类加载器,负责加载并初始化 类文件,得到真正的Class类,即模板。

Car Class:由Car.class字节码文件,通过ClassLoader加载并且初始化得到,这个Car就是当前类的模板,这个Car Class模板就存在方法区。

car1,car2,car3:是由Car模板经过实例化而得,一个模板可以获得多个实例化对象。

拿car1举例,car1.getClass()可以得到模板Car类,Car.getClassLoader()可得到其装载器

种类

一共有四类。

虚拟机自带的加载器:

启动类加载器,也叫根加载器(BootStrap)。由C++编写,程序中自带的类,存储在 $JAVAHOME/jre/lib/rt.jar中,如object类等

扩展类加载器(Extension),Java编写,在我们看到的类路径中,凡是以Javax开头的,都是拓展包,存储在 $JAVAHOME/jre/lib/ext/*.jar 中

应用程序类加载器(AppClassLoader),即平时程序中自定义的类 new出来的

用户自定义的加载器:

Java.lang.ClassLoader的子类,用户可以定制类的加载方式,即如果你的程序有特殊的需求,你也可以自定义你的类加载器的加载方式 ,进入ClassLoader的源码,其为抽象类,因此在你定制化开发的时候,需要你定义自己的加载器类来继承ClassLoader抽象类即可,即 MyClassLoader extends ClassLoader

Java的类加载机制,永远是以 根加载器->拓展类加载器->应用程序类加载器 这样的一个顺序进行加载的。如下图:

4.双亲委派机制

双亲委派机制:安全

  1. APP–>EXC—B00T(最终执行)
    BOOT
    EXC
    APP

概念:

1.类加载器收到类加载的请求

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

3.启动加载器是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载

4.重复步骤3

举个例子:

有一个类A.java,当要使用A类时,类加载器要先去根加载器(BootStrap)中去找,如果找到就使用根加载器中的A类,不继续往下执行,但是如果找不到,则依次下放,去拓展类加载器中找,同理找到就用,找不到继续下放,再去应用程序类加载器中找,找到就用,找不到就报classNotFound Exception的异常。

采用双亲委派机制的好处就是不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同一个Object对象。

5.沙箱安全机制

Java安全模型的核心就是ava沙箱(sandbox) , 什么是沙箱?沙箱是一个限制程序运行的环境。 沙箱机制就是
将Java代码限定在虚拟机JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保
证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么? CPU、内
存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不- -样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。
在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信
的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙
箱(Sandbox)机制。如下图所示JDK1.0安全模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1Sg1QTi-1667547104317)(C:\Users\21886\Desktop\学习\JVM\image-20220418173613428.png)]

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

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

在这里插入图片描述

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

在这里插入图片描述

组成沙箱的基本组件:

●字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保
护。但并不是所有的类文件都会经过字节码校验,比如核心类。
●类装载器(class loader) :其中类装载器在3个方面对Java沙箱起作用
。它防止恶意代码去干涉善意的代码;
。它守护了被信任的类库边界;
。它将代码归入保护域,确定了代码可以进行哪些操作。
虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成, 每一个被装载的
类将有一-个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。
类装载器采用的机制是双亲委派模式。
1.从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
2.由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然
无法生效。
●存取控制器(access controller) :存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略
设定,可以由用户指定。
●安全管理器(security manager) :是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优
先级高。
●安全软件包(security package) : java.security 下的类和扩展包下的类,允许用户为自己的应用增加新的安
全特性,包括:

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

6.Native

native :凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库!
会进入本地方法栈
调用本地方法本地接口JNI(Java Native Interface)
JNI作用:扩展Java的使用,融合不同的编程语言为Java所用!最初: C. C++。
Java诞生的时候C、C++横行,想要立足,必须要有调用C、C++的程序
它在内存区域中专门开辟了-标记区域: Native Method Stack, 登记native 方法
在最终执行的时候,加载本地方法库中的方法通过INI
Java程序驱动打印机,管理系统,掌握即可, 在企业级应用中较为少见!

private native void starte();

Native Method Stack

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

7.PC寄存器

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

8.方法区

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

9.栈

线程每调用一个方法,就会有一个栈帧入栈

喝多了吐就是栈,吃多了拉就是队列
为什么main()先执行,最后结束~
栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题
一旦线程结束,栈就Over!

int short long byte double float boolean char栈: 8大基本类型+对象引用+实例的方法
栈运行原理:栈帧
栈满了: StackOverflowError

在这里插入图片描述

在这里插入图片描述

10.三种JVM

sun Java HotSpot™ 64-Bit Server VM (build 25.321-b07, mixed mode)

BEA JRockit

IBM J9VM

11.堆

Heap,-一个VM只有一个堆内存, 堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中?类, 方法,常量,变量~,保存我们所有引用类型的真
实对象; .
堆内存中还要细分为三个区域:
●新生区(伊甸园区) Young/New
●养老区old
●永久区Perm

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G1HRpAjv-1667547104324)(C:\Users\21886\Desktop\学习\JVM\image-20220419110317550.png)]

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

假设内存满了,00M,堆内存不够! java.lang.OutOfMemoryError: Java heap space
在JDK8以后,永久存储区改了个名字(元空间) ;|
在这里插入图片描述

12.新生区

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

13.老年区

在这里插入图片描述

14.永久区

这个区域常驻内存的。用来存放DK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境或者类信息,这个区域不存在垃圾回收,关闭JVM虚拟机就会释放这个区域的内存

一个启动类,加载了大量第三方的jar包,或者tomcat部署了太多的应用,大量动态生成的反射类,不断被加载,直到内存满,就会出现OOM

●jdk1.6之前:永久代,常量池是在方法区;
jdk1.7:永久代,但是慢慢的退化了,去永久代,常量池在堆中
●jdk1.8之后:无永久代,常量池在元空间

在这里插入图片描述

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

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=" + max + "字节\t" + (total/(double)1024/1024) + "MB");

    //默认情况下 分配的总内存为电脑内存的1/4  初始化的总内存为1/64

    //00M:
    //1.尝试扩“大堆内存看结果
    //2.分析内存,看一下那个地方出现了问题(专业工具)

    //-Xms1024m -Xmx1024m -XX:+PrintGCDetails 手动调参堆内存的空间
// -Xms 设置初始化内存分配大小
 // -Xmx 设置最大分配内存,默认1/4
//-Xms1m -Xmx8m -XX: +HeapDump0n0utOfMemoryError

15.堆内存调优

使用jprofiler工具分析OOM原因

byte[] array = new byte[1*1024*1024];

public static void main(String[] args) {
    ArrayList<Demo02> list = new ArrayList<>();
    int count = 0;


    try{
        while (true){
            list.add(new Demo02());
            count = count + 1;
        }
    } catch (Exception e) {
        System.out.println("count" + count);
        e.printStackTrace();
    }

}

16.GC:垃圾回收

GC的执行区,只有堆和方法区

JVM在进行GC时,并不是对这三个区域统一回收。大部分时候,回收都是新生代~
●新生代
●幸存区(form, to)
●老年区
GC两种类别:轻GC (普通的GC), 重GC (全局GC)
GC题目:
●JVM的内存模型和分区~详细到每个区放什么?
●堆里面的分区有哪些? Eden, form, to, 老年区,说说他们的特点!
●GC的算法有哪些?标记清除法,标记压缩,复制算法,引用计数器,怎么用的? I
●轻GC和重GC分别在什么时候发生?

17.常用算法

引用计数法

在这里插入图片描述

复制算法

在这里插入图片描述在这里插入图片描述

●好处:没有内存的碎片~
●坏处:浪费了内存空间~ :多了- -半空间永远是空to。假设对象100%存活(极端情况)

复制算法最佳使用场景:对象存活度较低的时候;新生区~

标记清除算法

在这里插入图片描述

优点:不需要额外的空间

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

标记压缩 再优化

在这里插入图片描述

标记清除压缩

在这里插入图片描述

再压缩

在这里插入图片描述

18.总结

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

没有最好的算法,只有最合适的算法---- GC :分代收集算法
年轻代:

●存活率低
●复制算法!

老年代:

●区域大:存活率
标记清除+ 标记压缩混合实现

JMM

JMM介绍

Java虚拟机是一个实现了跨平台的虚拟系统,因此它也有自己的内存模型,即Java内存模型(Java Memory Model, JMM)。

Java Memory Model(Java内存模型), 围绕着在并发过程中如何处理可见性、原子性、有序性这三个特性而建立的模型。

作用:

缓存一致性协议,用于定义数据读写的规则(遵守,找到这个规则)。
JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)
中,每个线程都有一个私有的本地内存(Local Memory)

JMM的主要目标是定义程序中变量的访问规则,如下图,所有的共享变量都存储在主内存中共享,每个线程拥有自己的工作内存(相当于高速缓存,有利于提高访问速度),工作内存中保存的是主内存中变量的副本,线程对变量的读写操作是在自己的工作内存中进行,而不是直接读写主内存中的变量。如此多线程进行数据操作时,将可能发生线程安全问题,因此JMM需要提供原子性、可见性、有序性的保证。
在这里插入图片描述

解决共享对象可见性这个问题: volilate

JMM规定

按顺序执行read--load,且不可单独出现。
按顺序执行store-- write,且不可单独出现。
有assign操作后,变量改变后,需要同步到主内存中。
新变量必须诞生在主内存中,不可使用未load过的变量。
一个变量同一时刻只允许一个线程lock它。且一个lock就要一个unlock解开。
对变量lock操作,将会清空所有本地内存中此变量。
unlock一个变量前必须有store 和 write操作

JMM对这八种指令的使用,制定了如下规则:
■ 不允许read和load、 store和write操作之 一单独出现。 即使用了read必须load,使用了store必须
write
■不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存.
■不允许一个线程将没有assign的数据从工作内存同步回主内存
■一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实
施use、store操作之前,必须经过assign和load操作
■一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
■如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必
须重新load或assign操作初始化变量的值
■如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock- 个被其他线程锁住的变量
■对一个变量进行unlock操作之前,必须把此变量同步回主内存
JMM对这八种操作规则和对yolatile的- -些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安
全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多
数据从工作内存同步回主内存
■一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实
施use、store操作之前,必须经过assign和load操作
■一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
■如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必
须重新load或assign操作初始化变量的值
■如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock- 个被其他线程锁住的变量
■对一个变量进行unlock操作之前,必须把此变量同步回主内存
JMM对这八种操作规则和对yolatile的- -些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安
全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多
的时候,使用java的happen-before规则来进行分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值