JVM 介绍与应用

JVM 的位置:

​ JVM是运行在操作系统之上的,与硬件没有直接的交互,但是可以调用底层的硬件,用JNI (Java本地接口 调用底层硬件接口,了解下就好,已经过时了)

在这里插入图片描述

JVM 的体系结构:

在这里插入图片描述

类加载器:

​ JVM会通过 加载、连接、初始化 3个步骤来对该类进行初始化

​ 根类加载器:bootstrap class loader

​ 扩展类加载器:extensions class loader

​ 系统类加载器:system class loader

​ 用户类加载器:application class loader

在这里插入图片描述

在这里插入图片描述

双亲委派机制:
双亲委派机制,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式.
即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。

双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
沙箱安全机制:
什么是沙箱?
Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

java中的安全模型:
 在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。如下图所示

在这里插入图片描述

Native:
# JNI:(Java Native Interface 本地方法接口)
JNI 作用:扩展 Java 的使用,融合不同的编程语言为 Java 所用!

native:带有 native 关键字的,说明 java 的作用范围达不到了,会去调用底层c语言的库
# 凡是带了 native 关键字的方法就会进入本地方法栈,其它的就是 Java 栈
# 进入本地方法栈 ----> 调用本地方法接口 JNI  
native 位置:Native Method Stack(本地方法栈),存在内存区域

# native 方法一般用于:Java 程序驱动打印机,管理系统等硬件。

PC 寄存器:
# 程序计数器:Program Counter Register
	每个线程都有一个程序计数器, 是线程私有的, 就是一个指针, 指向方法区中的方法字节码(用来存储指向像一条指令的地址, 也即将要执行的指令代码), 在执行引擎读取下一条指令, 是一个非常小的内存空间, 几乎可以	 忽略不计
方法区:
Method Area(方法区): 
	方法区是被所有线程共享, 所有字段和方法字节码, 以及一些特殊方法, 如构造函数, 接口代码也在此定义, 简单说, 所有定义的方法的信息都保存在该区域, 此区域属于共享区间。
	
	静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中, 但是 实例变量 存在堆内存中, 和方法区无关。(static, final, Class, 常量池)
栈:数据结构
栈:先进后出、后进先出(桶)(main方法最后结束也是典型)
队列:先进先出(FIFO:First Input First Output, 管道)

# 栈溢出:形成方式典型是 递归方法 造成的!也就是 a()调用b(), b()调用a() 反复调用!

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

# 栈包括:8大基本类型 + 对象引用 + 实例的方法(意思是:栈帧调用的时候)
# 方法执行的指令存在于方法区,但是在栈中会给每个方法开辟空间用于存放方法中的局部变量

# 栈运行原理:栈帧(Stack Frame)
# 栈帧的子帧指向下一个方法,栈帧的父帧指向上一个方法!
# 在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。
堆:
Heap, 一个JVM只有一个堆内存, 堆内存的大小可以调节。
类加载器读取了类文件后,一般把 类的具体对象、方法、常量、变量, 保存所有引用类型的真实对象。

# 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。

堆内存细分三个区域:
	新生区:生成区、 幸存区:0区, 1区
	养老区:
	永久区:在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),# 元空间与永久代类似,都是方法区的		       实现#,他们最大区别是:元空间并不在JVM中,而是使用本地内存。
	
移除永久代原因:为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代。
			 有了元空间就不再会出现永久代OOM问题了!

在这里插入图片描述

# 出现OOM的原因:
	1. Java 堆空间溢出:应用程序无意中保存了对象引用,对象无法被 GC 回收
	2. GC 开销超过限制:Java 进程98%的时间在进行垃圾回收,恢复了不到2%的堆空间,最后连续5个(编译时常		量)垃圾回收一直如此。
	3. 请求的数组大小超过虚拟机限制:应用程序试图分配一个超过堆大小的数组
	4. 无法新建本机线程:内存不足,无法创建新线程。由于线程在本机内存中创建,报告这个错误表明本机内存空		  间不足。(因为元空间满了)	解决方案:为机器分配更多的内存, 减少 Java 堆空间

# 解决OOM:1.尝试扩大堆内存;2.分析内存,看一下哪些地方出现问题(有idea插件工具可以查找)

# 绝大多数的对象都是临时对象!
永久区:
	这个区域常驻在内存的。用来存放JDK自身携带的Class对象。Interface元数据, 存储的是 Java 运行时的一些环境或类信息, 这个区域不存在垃圾回收!关闭VM虚拟就会释放这个区域的内存。
	
造成永久区OOM:
	一个启动类加载大量的第三方jar包。tomcat部署大量应用。大量动态生成反射类。不断被加载,知道内存满。
	jdk1.6 之前:永久代, 常量池在方法区;
	jdk1.7:永久代, 慢慢退化, 去永久代, 常量池在堆中
	jdk1.8之后:无永久代, 常量池在元空间
# 元空间与永久代类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。		
	
# Runtime.getRuntiom().maxMemory() 返回虚拟机视图使用的最大内存
# Runtime.getRuntiom().totalMemory() 返回jvm的初始化总内存
# 扩大堆内存:在 VM options 输入:-Xms 1024m -Xmx1025m -XX

在这里插入图片描述

JPofiler 工具:
# idea 内存快照分析工具:JPofiler
# eclipse 内存快照分析工具:MAT
# 作用:分析Dump内存文件,快速定位内存泄露、获得堆中的数据、获得大的对象...

# -Xms 设置初始化内存分配大小  默认大小:1/64(本机内存)
# -Xmx 设置最大分配内存		  默认大小:1/4(本机内存)
# -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError  打印栈溢出,OOM错误的日志 oom dumo文件
# -XX:+PrintGCDetails 打印GC垃圾回收器的一些信息

# JPofiler 使用:
1. 去官网下载安装
2. 在 idea setting栏目JPofiler下录入下载程序的 .exe 程序运行单元
3. 在VM Options 中设置 -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
4. 启动程序报错可以在项目父目录下看到 .hprof 文件,使用JPofiler工具打开即可查看错误信息!
GC:
# JVM在进行GC时,大部分时候都在回收新生区
# GC两种类:轻GC(普通GC),重GC(全局GC:全局回收)

# 不需要进行垃圾回收的区域:本地方法栈、程序计数器、虚拟机栈。
# 因为他们的生命周期是和线程同步的,随着线程的销毁,他们占用的内存会自动释放。
# 所以,只有方法区和堆区需要进行垃圾回收,回收的对象就是那些不存在任何引用的对象。

# 查找算法: 
引用计数算法:
	每个对象添加到引用计数器,每被引用一次,计数器+1,失去引用,计数器-1,当计数器在一段时间内为0时,即认为该对象可以被回收了。但是这个算法有个明显的缺陷:当两个对象相互引用,但是二者都已经没有作用时,理应把它们都回收,但是由于它们相互引用,不符合垃圾回收的条件,所以就导致无法处理掉这一块内存区域。因此,Sun的JVM并没有采用这种算法,而是采用一个叫——根搜索算法
	
补充概念,在JDK1.2之后引入了四个概念:强引用、软引用、弱引用、虚引用。
       强引用:new出来的对象都是强引用,GC无论如何都不会回收,即使抛出OOM异常。
       软引用:只有当JVM内存不足时才会被回收。
       弱引用:只要GC,就会立马回收,不管内存是否充足。
       虚引用:可以忽略不计,JVM完全不会在乎虚引用,作用就是做一些跟踪记录,辅助finalize函数的使用。

# 什么样的类需要被回收:
1. 该类的所有实例都已经被回收;
2. 加载该类的ClassLoad已经被回收;
3. 该类对应的反射类java.lang.Class对象没有被任何地方引用。

# 当一个对象经历了 15 次(默认)GC还没被清除掉就会进入老年区
# -XX:MaxTenuringThreshold=9999 通过这个参数可以设定进入老年代的时间(属于JVM调优的方式)
# GC 算法:
# 复制:(新生代的Eden区就是采用这种算法)
复制算法采用的方式为从根集合进行扫描,将存活的对象移动到一块空闲的区域。

# 优点:
不会有内存碎片产生
# 缺点:
当存活的对象较少时,复制算法会比较高效,其带来的成本是需要一块额外的空闲空间和对象的移动

额外空间就是 幸存区 的 FromSpace和 ToSpace

在这里插入图片描述

# 标记-清除:(老年区常用)
该算法采用的方式是从跟集合开始扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,并进行清除。

蓝色部分是有被引用的对象,褐色部分是没有被引用的对象。在Marking阶段,需要进行全盘扫描,这个过程是比较耗时的。

在这里插入图片描述

清除阶段清理的是没有被引用的对象,存活的对象被保留。

标记-清除动作不需要移动对象,且仅对不存活的对象进行清理,在空间中存活对象较多的时候,效率较高,但由于只是清除,没有重新整理,因此会造成内存碎片。

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

在这里插入图片描述

# 标记-压缩:(该算法适用于旧生代:老年区)
该算法与标记-清除算法类似,都是先对存活的对象进行标记,但是在清除后会把活的对象向左端空闲空间移动,然后再更新其引用对象的指针

由于进行了移动规整动作,该算法避免了标记-清除的碎片问题,但由于需要进行移动,因此成本也增加了。

在这里插入图片描述

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

# 没有最好的算法,只有最合适的算法。==========>分代收集算法。(每个区算法不同)
JMM:
# Java Memory Model(Java 内存模型)


JQU-1618489419889)]

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

# 没有最好的算法,只有最合适的算法。==========>分代收集算法。(每个区算法不同)
JMM:
# Java Memory Model(Java 内存模型)


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值