一、JVM相关概念
1-JVM的位置
2-JVM的体系结构
JVM调优:99%都是调的方法区和堆
本地方法栈会去调用本地方法接口
本地方法接口:java native interface,简称JNI。主要是用来调用其他的语言的东西,扩展java的使用,融合不同的编程语言为Java所用!最初: C、C++
凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层c语言的库!
会进入本地方法栈
调用本地方法本地接口JNI
Java诞生的时候C、 C++ 横行,想要立足,必须要有调C、C++ 的程序
它在内存区域中专门开辟了一块标记区域: Native Method stack,登记native方法
在最终执行的时候,加载本地方法库中的方法通过JNI
例如:Java程序驱动打印机,管理系统,掌握即可,在企业级应用比较少
private native void start0();
调用其他接口:现在有很多方式,比如:Socket. . WebService~. .http~
3-三种JVM
首先咱们使用cmd,输入java -version
查看,本机的jvm类型:
- Sun公司HotSpot Java Hotspot
- BEA JRockit
- IBM J9VM
我们学习都是: Hotspot
二、类加载器
作用:加载class文件
下图是类在经过类加载器(classLoader)后的变化
以上图解为图中几个部分的相互关系以及获取方式
- 虚拟机自带的加载器
- 启动类(根)加载器,rt.jar
- 扩展类加载器,ext目录下的jar
- 应用程序加载器
每个加载器加载哪些jar,根加载器加载rt.jar 扩展类加载器加载ext目录下的jar
-
双亲委派机制:主要是保证安全,一级一级的找
- 程序执行首先一级级的走加载器,APP加载器–->EXC加载器—>BOOT加载器
- 如果BOOT加载器没有这个类就倒着走回去,BOOT加载器—>EXC加载器—>APP加载器,直到在一级中运行
- 其实就是每个类加载器都很懒都想让父类(只是字段并非真正的父类)来加载,父类加载不了的才自己加载(懒鬼)
-
类加载过程再理解:
- 类加载器收到类加载的请求
- 将这个请求向上委托给父类加载器去完成,一 直向上委托,知道启动类加载器
- 启动加载器检查是否能够加载当前这个类,能加载就结束, 使用当前的加载器,否则, 抛出异常,通知子加载器进行加载
- 重复步骤3,如果最后还是没找到,Class Not Found异常就是这么来的
- 注:有些时候你使用getclassloader返回的是null,可能是因为java调用不到,可能触及到了C++的部分了
1-类加载器的类别
BootstrapClassLoader(启动类加载器)
c++
编写,加载java
核心库 java.*
,构造ExtClassLoader
和AppClassLoader
。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
ExtClassLoader (标准扩展类加载器)
java
编写,加载扩展库,如classpath
中的jre
,javax.*
或者
java.ext.dir
指定位置中的类,开发者可以直接使用标准扩展类加载器。
AppClassLoader(系统类加载器)
java编写,加载程序所在的目录,如`user.dir`所在的位置的`class
CustomClassLoader(用户自定义类加载器)
java
编写,用户自定义的类加载器,可加载指定路径的class
文件
2-什么是双亲委派机制
当某个类加载器需要加载某个.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
3-双亲委派机制的作用
1、防止重复加载同一个.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class
不能被篡改。通过委托方式,不会去篡改核心.class
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class
对象。这样保证了Class
执行安全。
三、 沙箱安全机制
1-基本概念
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)
2-组成沙箱的基本组件
-
字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类,String什么的。
-
类裝载器(class loader) :其中类装载器在3个方面对Java沙箱起作用
- 它防止恶意代码去干涉善意的代码
- 它守护了被信任的类库边界
- 它将代码归入保护域,确定了代码可以进行哪些操作
四、JVM的基本结构
1-Native Method Stack
它的具体做法是Native Method Stack中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies。[本地库]
2-Native Interface本地接口
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序, Java在诞生的时候是C/C++横行的时候,想要立足,必须有调用C、C++的程序,于是就在内存中专门开辟了块区域处理标记为native的代码,它的具体做法是在Native Method Stack 中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍!
3-PC寄存器
程序计数器: Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针, 指向方法区中的方法字节码(用来存储指向像一条指令的地址, 也即将要执行的指令代码),在执行引擎读取下一条指令, 是一个非常小的内存空间,几乎可以忽略不计
4-方法区 Method Area
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
5-栈内存
栈:先进后出
桶:后进先出
队列:先进先出( FIFO : First Input First Output )
栈:栈内存,主管程序的运行,生命周期和线程同步
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题
一旦线程结束,栈就Over!
栈内存中:
8大基本类型+对象引用+实例的方法
栈运行原理:栈帧
栈满了: StackOverflowError
6-堆内存
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中?
类,,方法,常量,变量,保存我们所有引用类型的真实对象;
堆内存中还要细分为三个区域:
- 新生区(伊甸园区):
- 养老区old:
- 永久区Perm:
GC垃圾回收,主要是在伊甸园区和养老区~
假设内存满了,OOM,堆内存不够! java.lang.OutOfMemoryError:Java heap space
永久存储区里存放的都是Java自带的 例如lang包中的类 如果不存在这些,Java就跑不起来了
在JDK8以后,永久存储区改了个名字(元空间)
6-1.新生区
类:诞生和成长的地方,甚至死亡;
内部包含:
- 伊甸园,所有的对象都是在伊甸园区new出来的!
- 幸存者区(0区,1区)
伊甸园满了就触发轻GC,经过轻GC存活下来的就到了幸存者区,幸存者区满之后意味着新生区也满了,则触发重GC,经过重GC之后存活下来的就到了养老区。
6-2.老年区
没啥说的
6-3.永久区
这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境~ 这个区域不存在垃圾回收,关闭虚拟机就会释放此区域的内存
- jdk1.6之前:永久区叫永久代,常量池是在方法区;
- jdk1.7: 永久区叫永久代,但是慢慢的退化了,提出
去永久代
,常量池在堆中 - jdk1.8之后:无永久代,常量池在元空间
元空间:逻辑上存在,物理上不存在 (因为存储在本地磁盘内) 所以最后并不算在JVM虚拟机内存中,此乃算出来的结果
6-4.堆内存调优
了解三个概念:
- -Xss:规定了每个线程虚拟机栈及堆栈的大小,一般情况下,256k是足够的,此配置将会影响此进程中并发线程数的大小。
- -Xms:表示初始化JAVA堆的大小及该进程刚创建出来的时候,他的专属JAVA堆的大小,一旦对象容量超过了JAVA堆的初始容量,JAVA堆将会自动扩容到-Xmx大小。
- -Xmx:表示java堆可以扩展到的最大值,在很多情况下,通常将-Xms和-Xmx设置成一样的,因为当堆不够用而发生扩容时,会发生内存抖动影响程序运行时的稳定性。
示例程序:
public class xmX {
public static void main(String[] args) {
String s = "";
while (true) {
s += "11111111111111111111111111111111111111111111111111111"
+new Random().nextInt(999999999)
+new Random().nextInt(999999999)
+new Random().nextInt(999999999);
}
}
}
修改参数:
-Xms1m -Xmx1m -XX:+PrintGCDetails
#设置初始化堆大小为1m,堆能达到的最大值为1m
运行之后,发现错误:
6-5.出现OOM排错
在一个项目中,突然出现了OOM故障,那么该如何排除 研究为什么出错~
- 能够看到代码第几行出错:内存快照分析工具,MAT,Jprofiler
- Debug,,一行行分析代码!
MAT一般在eclipse中不使用
JProfiler:
可以分析Dump内存文件,快速定位内存泄漏
获取堆中的数据
获取大的对象
Jprofile的安装:
- 官网下载https://www.ej-technologies.com/products/jprofiler/overview.html
- 安装,注意安装路径不要有中文空格之类
- 双击打开软件后,界面如下:
五、GC:垃圾回收
1-GC的作用区域
- JVM在进行GC时,分为轻GC(普通的GC),重GC (全局GC)
- JVM堆里面的分区有哪些?Eden,from,to,老年区
- JVM的内存模型和分区,详细到每个分区
堆里面的分区有哪些?
Eden,form,to,老年区,说说他们的特点!
2-GC的算法
标记清除法,标记整理,复制算法,引用计数器
2-1.引用计数法:
比如:对象A被使用了一次就计一个1,对象b被使用了两次就计数2。对象c没被使用就干掉。这种方法不高效
2-2.复制算法:
复制算法大致流程:
- 深绿色表示的是对象,现在图中幸存区下部分有对象,所以是from,上部分为空所以为to。然后执行一次GC(红色箭头),eden区对象会到to中,然后from中的对象也会去to中
- 第一步执行完毕后,由于之前的from区空了,所以变为to,之前的to区有对象了,所以为from。然后不断的循环1,2步,直到对象活到养老区,或者被GC掉。
- 好处:没有内存的碎片
- 坏处:浪费了内存空间 :多了一半空间永远是空to。假设对象100%存活(极端情况)
2-3.标记清除算法:
- 优点:不需要额外的空间!
- 缺点:两次扫描,严重浪费时间,会产生内存碎片
2-4.标记清除压缩算法:
如图:三步走
3-算法优劣总结:
- 内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)
- 内存整齐度:复制算法 = 标记压缩算法 > 标记清除算法
- 内存利用率:标记压缩算法 = 标记清除算法 > 复制算法
思考一个问题:难道没有最优算法吗?
答案:没有,没有最好的算法,只有最合适的算法—GC:分代收集算法
年轻代:
- 特点:存活率低
- 适合:复制算法
老年代:
- 特点:区域大、存活率高
- 适合:标记清除 + 标记压缩混合实现
六、JMM科普
这里与JUC有点关系了
1-什么是JMM
JMM:java memory model,即java内存模型
2-作用
作用:缓存一致性协议,用于定义数据读写的规则。
JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)
中,每个线程都有一个私有的本地内存(Local Memory)
图中三个线程,均从主内存中拷贝了a=1。这里会涉及到一个实时共享a=1的问题,每个线程在独自处理的时候不能够实时的监控主内存中a=1的改变。这里学了JUC知道了,目前了解即可。