JVM:
运行在操作系统之上,与硬件没有交互关系!
1. 类加载器ClassLoader
负责加载class文件,文件的入口!
启动类加载器: 负责加载$JAVA_HOME中jre/lib/**rt.jar**里所有的class!
扩展类加载器: 负责加载$JAVA_HOME中jre/lib/**ext/*.jar 里所有的class!
应用程序类加载器(AppClassLoader)Java
这三种都是系统自带的!
用户自定义的加载器!
重点:加载过程:必会!
双亲委派机制:当接收到加载请求时,会向上找父类加载器去完成!如果父类加载器没有,则再找子类!
"沙箱安全机制":不允许用户重写扩展类或启动类加载器中的类!
2. 执行引擎Execution Engine
解释命令,交个操作系统运行!
3. 本地接口Native Interface
方法有Native 修饰! 这个方法是由c,c++语言编写!
4. Native Method Stack
执行引擎发现有本地方法时,加载本地方法库! .dll 文件。
5. PC寄存器
**用来存储指向下一条指令的地址**
执行引擎在执行的时候,读取的PC 寄存器的指令地址,可以忽略不计!
6. Method Area方法区 {非堆}
共享区间:
存储的内容:
**静态变量+常量+类信息(构造方法/接口定义)+运行时常量池**存在方法区中
But
**实例变量存在堆内存**中,和方法区无关
static修饰
final 修饰
class Stu{
public result = 0;
Stu(){
};
Stu(int id,String name,String addr){
this.id = id;
this.name = name;
this.addr = addr;
};
private int id;
private String name;
private String addr;
...
public void sayHi(){
System.out.println("hello word");
}
public static void hello(){
System.out.println("hello");
}
}
Stu stu = new Stu(1,"张三","北京昌平");
stu:实例变量!
重点:
JVM:
栈:
栈内存,生命周期与线程一样!
栈中存储的数据:
8种基本类型的变量+对象的引用变量+实例方法!
int i = 10;
stu ; 实例变量,对象的引用变量
stu.sayHi(); 实例方法 / hello() 静态方法!
数据以栈帧格式存在:
本地变量:
栈操作:
栈数据
运行原理:先进后出 或 后进先出
Exception in thread "main" java.lang.StackOverflowError!栈内存溢出!
通常来将都是代码的问题! 出现循环递归引用,或互相引用!
堆:
1. 介绍 栈堆方法区的关系!
案例:
class Stu{
public result = 0;
// 构造存在方法区!
Stu(){
};
Stu(int id,String name,String addr){
this.id = id;
this.name = name;
this.addr = addr;
};
// 基本数据类型
private int id; // 栈
// String 是一个类,属于引用数据类型
// 但是String name 类没有new String(); 这个数据应该存在 方法区
// String s = new String("sss"); sss 存在堆中!
private String name;
private String addr;
...
// 栈
public void sayHi(){
System.out.println("hello word");
}
// 方法区
public static void hello(){
System.out.println("hello");
}
// 调用本地方法库,本地方法接口,本地方法 都是由 c/c++;
Thread.sleep(100);
}
Stu.class
Stu stu = new Stu(1,"刘德华","昌平");
通过类加载器 生成一个元数据模板!
Stu Class!
Stu stu1 = new Stu(1,"刘德华","昌平");
Stu stu2 = new Stu(2,"刘欢","昌平");
Stu stu3 = new Stu(3,"刘亦菲","昌平");
stu1 ,stu2, stu3 都是来在于同一个模板!Stu Class!
分析这个Stu Class 在哪? 在方法区!
stu1 ,stu2, stu3 实例变量 存在栈中!
(1,"刘德华","昌平");
(2,"刘欢","昌平");
(3,"刘亦菲","昌平");
以上数据存在堆!
2. 堆的体系结构:
堆的大小可以调节{内存调优指的就是堆内存调优}
加载的时候将数据进行分区保存!
jdk7 之前:
新生区:
是对象产生,消亡的过程!
养老区:
永久区:
不会产生垃圾回收!
永久区是方法区的一个实现!
jdk1.8 去永久代,将常量池放入堆中,引入了元空间 ,在本地内存中!元空间的大小受本地内存限制!
从 GC 角度划分:
Stu stu = new Stu(1,"刘德华","昌平"); // 会产生一个对象!
新生区:栈内存的1/3 !
伊甸区:新产生的对象 100 new Stu();
幸存0区 {form区/to区}: 存储没有被回收掉的对象!
幸存1区 {to区/form区}:
他们的比例关系: 8:1:1
当对象从from 区域 拷贝到 to区 的时候,这个对象年龄就会+1!
养老区:
默认经过15次GC 之后,依然存活的对象则会来到养老区!
MaxTenuringThreshold 默认值15 !
老年代不会频繁发生GC!
养老区如果满了,则会发生 FULL GC! 能够释放空间,能够存储对象了。如果不能释放空间了,但是还需要有对象进来,此时就会发生OOM!
OutOfMemoryError
产生原因:堆内存大小不够!
如果出现了内存溢出,那么解决方案!
通过参数调整内存大小!
-Xms、-Xmx
GC回收过程:必会!
MinorGC的过程:**复制** -> **清空** -> **互换**
from ,to区交换的时候,to区 总为空!
3. 堆参数调优入门
a. -Xmx50m -Xms30m -XX:+PrintGCDetails
4. 内存溢出分析工具:
MAT: 介绍!
jdk 自带工具: 找到哪个线程导致了OOM!
5. GC:分代收集算法!
a. 如何判断对象可以回收! 是否已经死亡!
1. 可以使用引用计数法!
维护一个计数器,记录当前这个对象被引用的次数!引用一次 +1 , 取消了引用则 -1;
优点:
简单
缺点:
1. +1 ,-1 性能低!
2. 循环引用依赖不好控制!
Java 中不使用!
Python 使用!
2. 可达性分析算法!
利用引用链与GC Roots 相连,如果能够链接到GC Roots 则表明当前对象不可回收,
如果不能链接到GC Roots 则表明可以回收!
重点: 真正标记以为对象为可回收状态至少要标记两次!
第一次标记:找到未与GC Roots 相连的对象!
第二次标记:判断当前这个对象是否实现了finalize(); 方法!如果实现了,这个对象也被标记了,可以回收!
finalize():
当对象被回收了,会立刻这个方法!
System.gc(): 程序员手动调用做垃圾回收!
当这个代码执行完,jvm 回立刻回收这个对象么?
不是!
final fianlly finalize 区别?
GC 特点:
- 次数上频繁收集Young区
- 次数上较少收集Old区
- 基本不动Perm区
四种引用:
强引用:不会被回收!
软引用:内存快要OOM 了,才会回收软引用对象!
弱引用:无论内存是否足够都会回收掉只被弱引用关联的对象!
虚引用:被收集器回收时收到一个系统通知
记住前两种!
垃圾回收算法:
**Stop-the-World**
只要有GC执行,执行的应用程序所有现在都停止!等待GC 所需的线程执行完成之后,才能继续执行!
优化GC 算法 通常就是指:Stop-the-world发生的时间!
复制算法(Copying):新生区
优点:
- 实现简单
- 不产生内存碎片
- 速度快!
缺点:
- 浪费内存空间!
标记清除:老年代
优点:
节省空间
缺点:
需要两次扫描!
产生内存碎片!
标记压缩:老年代
优点:
节省空间
不会产生内存碎片!
缺点:
需要两次扫描!
分代收集算法:
新生区: 复制算法!
老年代/养老区: 标记清除 或 清除与压缩混合使用!
6. 垃圾收集器 了解!
Serial/**Serial Old 串行收集器 单线程!
ParNew收集器: 多线程!
CMS收集器: 获取最短回收停顿时间为目标的收集器
具体做法:
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark)
- 并发清除(CMS concurrent sweep)
**优点**: 并发收集、低停顿
**缺点**: 产生大量空间碎片、并发阶段会降低吞吐量
G1收集器:最好!
1. 并发与并行: 能够减少stop-The-World停顿时间!
2. 分代收集: G1 有自己的收集方式!
3. 空间整合: G1 没有内存碎片!
4. 可预测的停顿: 能够预知停顿时间!
收集过程:
1、初始标记(Initial Making)
2、并发标记(Concurrent Marking)
3、最终标记(Final Marking)
4、筛选回收(Live Data Counting and Evacuation)
垃圾回收器选择策略 :
客户端程序 : Serial + Serial Old;
吞吐率优先的服务端程序(比如:计算密集型) : Parallel Scavenge + Parallel Old;
响应时间优先的服务端程序 :ParNew + CMS。
G1收集器是基于标记整理算法实现的,不会产生空间碎片,可以精确地控制停顿,将堆划分为多个大小固定的独立区域,
并跟踪这些区域的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(Garbage First)。