根据以下学习视频,个人整理的笔记
https://www.bilibili.com/video/BV1iJ411d7jS?spm_id_from=333.999.0.0&vd_source=7a8946d22777450e46486d5fd60d8d4d
JVM探究
- 请你谈谈你对JVM的理解?java8虚拟机和之前的变化更新?
- 什么是OOM?什么是栈溢出StackOverFlowError?怎么分析?
- JVM的常用调优参数有哪些?
- 内存快照如何抓取?怎么分析Dump文件?知道吗?
- 谈谈JVM中,类加载器你的认识?
我们需要知道
-
JVM的位置
-
JVM的体系结构
-
类加载器
-
双亲委派机制
-
沙箱安全机制
-
本地方法接口
-
PC寄存器
-
方法区
-
栈
-
三种JVM
-
堆
-
新生区、老年区
-
永久区
-
堆内存调优
-
GC垃圾回收器
-
JMM
-
总结
学习JVM的方法
- 百度
- 思维导图
JVM的位置
JVM的体系结构
简略图
所谓的JVM调优,就是在堆和方法区里面调,方法区属于特殊的堆。99%的情况下都在调堆
一般引用对象都存放在栈里面,而对象具体的值都存放在堆里面
详细图
类加载器
作用:加载Class文件
类加载器分为几种
- Java虚拟机(JVM)自带的加载器
- 启动类(根)加载器(这个加载器,用getClassLoader方法一般调用不了,因为根加载器是用C++写的)
- 扩展类加载器
- 应用程序加载器
类加载器的级别(儿子—>父亲):应用程序加载器—>扩展类加载器—>启动类(根)加载器—>Java虚拟机自带的加载器
双亲委派机制
- 类加载器收到类加载的请求后,将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
- 启动类加载器检查是否能够加载当前这个类,能加载就结束,然后使用当前加载器,否则抛出异常,通知子类加载器进行加载
- 重复步骤2
沙箱安全机制
**Java安全模型的核心就是Java沙箱(sandbox) **
什么是沙箱?
沙箱是一个限制程序运行的环境。
沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
沙箱主要限制系统资源访问,那系统资源包括什么?
CPU、内存、文件系统、网络。
不同级别的沙箱对这些资源访问的限制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。
在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱Sandbox)机制。
如下图所示JDK1.0安全模型
但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的Java1.1版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。
如下图所示JDK1.1安全模型
在Java1.2版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。
如下图所示
当前最新的安全机制实现,则引入了域(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下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括
- 安全提供者
- 消息摘要
- 数字签名
- 加密
- 鉴别
Native
凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层C语言的库。会进入本地方法栈,然后调用本地方法接口(JNI:java native interface),本地方法接口会去调用本地方法库
本地方法接口的作用:
- 扩展Java的使用,融合不同的编程语言
Java诞生的时候,C、C++横行,想要立足,必须要有调用C、C++的程序
它在内存区域中,专门开辟了一块标记区域:Native Method Stack,专门登记 native 方法,最终在执行的时候,通过 JNI 加载本地方法库中的方法
PC寄存器
程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针, 指向方法区中的方法字节码(用来存储指向像一条指令的地址, 也即将要执行的指令代码),在执行引擎读取下一条指令, 是一个非常小的内存空间,几乎可以忽略不计
方法区
方法区 Method Area
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
方法区主要存放:静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池
但是实例变量存在堆内存中,和方法区无关
深入理解一下栈
栈内存:主管程序的运行,生命周期和线程同步
线程(main方法也属于一个线程,通常称为主线程)结束,栈内存也就释放了。
对于栈来说,不存在垃圾回收问题
栈主要存放:8大基本类型、对象引用、实例的方法
栈运行原理:栈帧
栈如果满了就会报栈溢出的错误!
理解一下 方法区、栈、堆 这三者关系
自行搜索了解:对象实例化在内存中的过程
三种JVM
-
Sun公司的 HotSpot
-
BEA公司的 JRockit
-
IBM公司的 J9VM
我们学习用的都是: HotSpot
走近HotSpot和堆
堆:Heap
一个JVM只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把什么东西放到堆中?
-
类
-
方法
-
常量
-
变量
-
保存我们所有引用类型的真实对象
堆内存中还要细分为三个区域
- 新生区 New
- 伊甸园区
- 幸存区0区
- 幸存区1区
- 养老区 old
- 永久存储区 Perm
GC垃圾回收,主要是在新生区和养老区。
假设内存满了,堆内存不够了。会报错误信息:java.lang.OutOfMemoryError:Java heap space
在永久存储区里存放的都是Java自带的,例如lang包中的类,如果不存在这些,Java就跑不起来了
在JDK8以后,永久存储区改了个名字,叫元空间
新生区、老年区
新生区:类 诞生、成长、死亡 的地方
新生区
- 伊甸园区(所有的对象都是在伊甸园区new出来的)
- 幸存者区0
- 幸存者区1
伊甸园区满了就触发轻GC,经过轻GC存活下来的就到了幸存者区,幸存者区满了之后意味着新生区都满了,则触发重GC,经过重GC之后存活下来的就到了养老区。如果新生区和养老区都满了,就会报OOM错误(OutOfMemoryError)。别忘了,堆内存满了,我们是可以手动设置的,具体方法可以上网查
真理:经过研究,99%的对象都是临时对象!
永久区
这个区域常驻内存的。用来存放JDK自身携带的Class对象、Interface元数据。存储的是Java运行时的一些环境或类信息
永久区不存在垃圾回收,关闭虚拟机就会释放这个区域的内存
- jdk1.6之前:永久区叫永久代,常量池放在方法区中
- jdk1.7:永久区叫永久代,但是慢慢的退化了,去永久代,常量池放在堆中
- jdk1.8之后:无永久代,常量池放在元空间(即永久区)
元空间:逻辑上存在,物理上不存在!
在一个项目中,突然出现了OOM错误,该如何解决,研究为什么出错?
- 扩大堆内存
- 使用内存快照分析工具:MAT、JProfiler
- Debug,一行行分析代码(如果项目很大,这种方式不可取!)
MAT、JProfiler的作用:
- 能够分析Dump的内存文件,快速定位内存泄露问题
- 获得堆中的数据
- 获得大的对象
- …
使用JProfiler工具分析OOM原因
使用JProfiler
-
IDEA直接安装JProfiler插件
-
https://www.ej-technologies.com/官网下载JProfiler,下载后安装
-
安装路径可以自定义
-
许可证信息
密钥:L-J13-crossover#3078354-b3guirqhtugw3#3585b
-
选择IDEA集成
-
可以选择不检查更新
-
使用案例:
编写一个OOMError的代码
package com.kuang;
import java.util.ArrayList;
// -Xms 设置初始化内存分配大小
// -Xmx 设置最大分配内存
// -XX:+PrintGCDetails //打印GC垃圾回收信息
// -XX:+HeapDumpOnOutOfMemoryError //oom dump
//-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
public class Demo03 {
byte[] array = new byte[1*1024*1024]; // 1M
public static void main(String[] args) {
ArrayList<Demo03> list = new ArrayList<Demo03>();
int count = 0;
try {
while (true){
list.add(new Demo03());
count = count + 1;
}
}catch (Error e){ //OOM是错误,不是异常,所以这里是捕获Error
System.out.println("count:"+count);
e.printStackTrace();
}
}
}
设置
输入内容
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
运行
打开文件分析
GC
GC:垃圾回收
GC的作用区域:方法区、堆
JVM在进行GC时,并不是对这三个区域统一回收。大部分的时候,回收都是新生代
- 新生区
- 新生代
- 幸存区(form、to)
- 老年区
GC两种类型
- 轻量级GC(普通GC)
- 重量级GC(全局GC)
GC题目:
- JVM的内存模型和分区~详细到每个区放什么?
- 堆里面的分区有哪些?Eden,form,to,老年区。并且说说他们的特点
- GC的算法有哪些?标记清除法,标记压缩法,复制算法,引用计数法。并且说说具体实现过程
- 轻GC和重GC分别在什么时候发生?
引用计数法:给每个对象计算使用了多少次,使用次数最少的回收。计数器本身也会有消耗,引用计数法现在基本不用
复制算法:
复制算法好处:没有内存的碎片
复制算法坏处:浪费了内存空间,多了一半空间永远是空的,to空间。
假设对象100%存活(极端情况) ,复制算法会出现问题。
复制算法最佳使用场景:对象存活度较低的时候,即新生区
标记清除法
优点:不需要额外的空间
缺点:两次扫描,严重浪费时间,会产生内存碎片
标记压缩法
标记压缩法是对标记清除法的优化,防治内存碎片产生
总结:没有最好的算法,只有最合适的算法
新生区:
- 存活率低
- 一般用复制算法
老年代:
- 区域大,存活率高
- 一般用 标记清除法 + 标记压缩法 混合实现
JMM
JMM:Java Memory Model(Java内存模型)
**什么是JMM?**百度
**JMM有什么用?**官方,博客,视频讲解
作用:缓存一致性协议,用于定义数据读写的规则(遵守规则)
JMM定义了线程工作内存和主内存之间的抽象关系;线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存
解决共享对象可见性这个问题:volatile
**JMM该如何学习?**官方,博客,视频讲解
学习新东西是常态!学习方式很重要!