前言
JVM的学习
JVM
Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java语言的运行环境,它是Java 最具吸引力的特性之一。(百度百科)
问题
对JVM的理解
jvm8虚拟机的变化
OOM
栈溢出StackOverFlowError
JVM常用调优参数
内存快照如何抓取
怎么分析Dump文件
JVM中的类加载器
JVM的位置
JRE包含JVM
Java程序-----JVM-----操作系统
JVM在操作系统层的上面
lombok插件在执行引擎上进行操作,来动态生成getset
JVM的体系结构
类加载器
作用:加载Class文件 new Student
类是一个模板是抽象的,对象是具体的
对象的引用在栈里,具体的在堆里
通过反射获得的Class对象,是唯一的
Class<Car>carclass = car.class;
//三个不同的对象,hashcode也不同
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
//将对象转化为类,hashcode相同
Class<? extends Car> carclass1 = car1.getClass();
Class<? extends Car> carclass2 = car2.getClass();
Class<? extends Car> carclass3 = car3.getClass();
ClassLoader classLoader = aClass.getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
/*
sun.misc.Launcher$AppClassLoader@18b4aac2应用程序加载器
sun.misc.Launcher$ExtClassLoader@1b6d3586扩展类加载器
null 1.不存在,2.java程序获取不到,,rt.jar
*/
等级:
虚拟机自带的加载器
启动类(根)加载器
扩展类加载器
应用程序(系统类)加载器
双亲委派机制
AppClassLoader应用程序(系统类)加载器
ExtClassLoader扩展类加载器
启动类(根)加载器,用C写获取不到
过程:
- 类加载器收到类加载的请求
- 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
- 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则抛出异常,通知子加载器进行加载
- 重复步骤3
package java.lang;
public class String{
public String toString(){
return "Hello"
}
public static void main(String []args){
String s = new String();
s.toString();
}
}
/*
报错,java.lang.String没有main方法
*/
双亲委派机制:为了保证安全
我们在APP应用程序加载器重新写了String类
但是由于双亲委派机制
他会从APP—>EXC----->ROOT(RT.JAR)
最终执行ROOT根加载器的String方法
若ROOT里没有,则从EXC中找,若EXC中没有,则在APP里找
沙箱安全机制
Java的安全模型的核心是沙箱安全机制
jdk1.1增加了安全策略,允许用户指定代码对本地资源的访问权限
jdk1.2增加了代码签名,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制
最新的安全机制为引入了域的概念,虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中的不同受保护域,对应的权限不同。存在于不同域中的类文件就具有了当前域的全部权限
沙箱的组成
字节码校验器:
用来检验变量使用之前有没有初始化,有没有违背私有属性、方法调用规则
类加载器:
防止恶意代码去干涉善意的代码(双亲委派)
守护了信任类库的边界
它将代码归入保护域,确定了代码可以进行哪些操作
Native本地方法接口
最为代表性的是Thread的start方法,有native start0方法
native关键字
native会进入本地方法栈,调用本地方法接口JNI,调用本地方法库
JNI作用:扩展Java的使用,融合不同的编程语言为Java使用。
他在内存中专门开辟了一块标记区域(本地方法栈),用来登记native方法,会在最终执行的时候去加载本地方法库中的方法,通过JNI
凡是带了native关键字的方法,说明java的作用达不到了,会调用底层c语言的库
因为Java开始的时候C和C++普及,必须要有调用C,C++的程序
例如Java程序驱动打印机,管理系统,Robot方法
调用其他语言的方法:Socket,webService,HTTP,RPC
PC寄存器
保证线程不乱,线程是私有的,就是一个指针,指向方法区的方法字节码,执行引擎的下一条指令
方法区
方法区被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享空间
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
static final Class类模板或者类加载器 运行时的常量池
栈
一种数据结构
栈,先进后出
队列,先进先出
栈:栈内存,主管程序的运行,生命周期和线程同步,线程结束 栈内存释放,不存在垃圾回收
一旦线程结束,栈就Over
栈存放:八大基本类型+对象引用+实例的方法
栈运行原理:栈帧
栈具体的存放:
栈+堆+方法区调用关系:
内存中对象实例化过程:
三种JVM
Sun公司的HotSpot
Oracle(原来BEA)的JRockit
IBM的J9VM
堆
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会保存
类具体的实例,方法,常量,变量····保存所有引用类型的真实对象
堆分新生区,老年区,永久区
新生区
垃圾回收分为两种:轻量级和重量级
新生区:一般是轻量级GC
可以说是一个,类:诞生和成长的地方,甚至死亡
包含三部分
-
伊甸园区
所有的对象都是在伊甸园区new出来的
若伊甸园区有10个位置,new了10对象后满了,触发轻GC
-
幸存0区From
-
幸存1区To
幸存区:经历N次垃圾回收还能存活的
幸存区每次清理会换位置
当幸存区和伊甸园区都满了,触发重GC(Full GC)活下来的进入老年区
经历30次GC后还能存活进入老年区
GC垃圾回收主要在伊甸园区和老年区
内存满了,(新生区,养老区满了),会报OOM错误,堆内存不够
老年区
老年区:一般是重量级GC:Full GC
元空间(永久区)
这个区域常驻内存,这个存放JDK自身携带的Class对象,包括一些接口数据(元数据),存储是java运行时的环境,这个不存在垃圾回收
当关闭虚拟机的时候,就会释放这个区域的内存
一个启动类加载了第三方Jar包,Tomcat部署了太多的应用,大量动态生成的反射类。不断地被加载,直到内存满,就会出现OOM
JDK8后,永久区叫元空间
jdk1.6之前,永久代,常量池在方法区
jdk1.7,永久代,慢慢退化,去永久代概念,常量池在堆中
jkd1.8后,无永久代,常量池在元空间
元空间中包含方法区
方法区也叫非堆
方法区里有个小地方叫常量池
将代码永久的保存起来,放到方法区,使用接口
从计算上的角度来看,堆只包括了新生代和老年代,不包含元空间(也就是永久区)
堆内存调优
默认情况下分配的总内存是电脑内存的1/4
初始化的内存是1/64
OOM方法:尝试1.扩大堆内存。若还有2.修改代码
命令例子:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
OOM排错方法:内存快照分析工具,MAT(esclipse)
JProfiler
作用:分析Dump内存文件,快速定位内存泄漏问题,获得堆中的数据,获得大的对象
Dump
首先加配置打印信息,当出现OOM,获得Dump文件,在项目的目录中,在文件夹中找
-Xms1m -Xmx8m -xx:+HeapDumpOnOutOfMemoryError
用JProfiler打开,看Biggest Objects,哪个对象的占用最大,然后在Heap walker中,看执行的线程哪个出错,会指出哪行的错误
GC垃圾回收器
GC的作用区在方法区和堆
JVM在进行GC时,并不是对三个区域(新生区,幸存区,老年区)统一回收。大部分是回收新生代
GC分两种,,轻GC和重GC
JVM的内存模型和分区,具体到放什么?
堆内存的分区?特点?
GC的算法有哪些?特点?怎么用?
标记清除法:
标记整理:
复制算法:将幸存From区的幸存对象复制到幸存To区,清空From变为To区
引用计数法:就是判断对象是否还有引用,当为0,清除,不过标记会消耗内存,不建议
复制算法:
轻GC和重GC在什么时候发生
幸存区分为From和To,谁空谁是To
每次GC都会从Eden的对象移到幸存区中,一旦Eden区被GC后就会是空的,
幸存区满了后,将幸存From区的幸存对象复制到幸存To区,清空From变为To区
经历15次GC还存活就会去老年区
老年区满了就会进行Full GC,如果还满OOM
Eden----To—复制算法-----From-----From和To交换----
好处没有内存碎片,
坏处:浪费一半幸存区空间
假设对象100%存活,把To区完整复制到From,会出现问题
复制算法最佳使用:对象存活率较低,所以最好是在新生区
标记清除算法:
标记:对所有对象进行标记,存活标记
清楚:对没有标记存活的对象,进行清楚
优点:不需要额外空间
缺点:标记会占内存,两次扫描时间有成本,会产生内部碎片
标记压缩:
再优化:防止内部碎片产生
再次扫描进行排序
内存效率:复制算法>标记清除算法>标记清除压缩(时间复杂度问题)
内存整齐度:复制算法=标记清除算法>标记清除压缩
内存利用率:标记清除算法=标记清除压缩>压缩复制算法
所以GC也称分代收集算法
年轻代:
存活率低,所以用复制算法
老年代:
存活率高,区域大,所以用标记清除(内部碎片少的时候)+标记压缩混合实现
所以JVM调优在调上面的东西