初识JVM
JVM探究
1. JVM的位置

2. JVM体系结构


3. 类加载器
作用:加载Class文件~
bootstrap class loader 根加载器
extension class loader 扩展类加载器
app class loader 系统类加载器
自定义加载器 继承classloader
他们都继承classloader这个抽象类
4. 双亲委派机制:安全
APP----->Exc-----Boot(最终执行)
- 类加载器收到类加载请求
- 将这个请求向上委托给父类加载器完成,一直向上委托,到启动类加载器
- 启动类检查是否可以加载,可以则结束,使用当前的加载器,否则抛出异常,通知子加载器进行加载
- 重复步骤 3
5. 沙箱安全机制
6. Native
凡是带了native关键字的,说明java的作用范围达不到了,回去调用C语言的库
会进入本地方法栈,调用本地方法接口 java native interface JNI 调用本地方法库
JNI 的作用:扩展Java的只用,融合不同的编程语言为Java所用
它在内存区域中开辟了一块标记区域, native Method stack,登记native 方法
最终执行的时候,加载本地方法库中的方法 通过JNI
7. PC寄存器
程序计数器
每一个线程都用一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码
例如 汇编语言中的 CS:IP
8. 方法区
方法区是被所有线程共享的,所有的字段,方法,字节码。以及一些特殊的方法,如构造函数,接口代码也在次定义,简单来说,所有定义的方法信息都保存在该区域,此区域属于共享区间
静态变量,常量,类信息(构造函数,接口定义),运行时常量池也存在方法区中,但是实例变量存在堆内存中,和方法区无关
static
final
Class
常量池
9. 栈 数据结构
参考汇编 Stack 结构 SS:SP
栈满 就Stack over flow error
正常递归调用的时候 最容易栈溢出
public class HelloLoader {
public static void main(String[] args) {
System.out.println("please loader me ,thank you");
HelloLoader helloLoader = new HelloLoader();
helloLoader.a();
}
public void a(){
b();
}
public void b(){
a();
}
}

吃多了吐就是栈,吃多了拉就是队列
线程结束栈内存也就得到了释放,不存在垃圾回收的问题
栈存储:八大基本类型
对象引用
示例的方法
10. 堆
Heap,一个JVM只有一个堆内存 堆内存的大小是可以条件的
类加载器读取类文件后,一般把什么放入堆中?
类、
方法
常量
变量
保存我们所有引用类型的真实对象

堆内存要细分3个区域
- 新生区 eden young
- 养老区 old
- 永久区 perm

GC垃圾回收 主要是在eden和养老区
堆内存不够 就会报OOM
import java.util.Random;
public class OOM {
public static void main(String[] args) {
String str = "immortal";
while (true){
str += str + new Random().nextInt(1_1111_1111)+ new Random().nextInt(1_1111_1111);
}
}
}

在jdk8以后,永久存储区 改了个名字叫元空间
新生区
- 类:诞生,成长,死亡的地方
- 伊甸园区 所有的对象都是在伊甸园区new出来的
- 幸存者区
- from
- to
真理:经过研究,99%的对象都是临时对象!
老年区

永久区
这个区域常驻内存的,用来存放JDK自身的CLass对象 java运行时的一些环境
一个启动类,加载了大量的第三方Jar包,Tomcat部署了太多应用,大量动态生成反射类,不断的被加载,知道内存满 就会OOM
jdk1.6 : 永久代 常量池在方法区
jdk1.7 :永久代 慢慢退化了 去永久代 常量池在堆中
jdk1.8之后 无永久带 常量池在元空间中

逻辑上存在,物理上不存在
上代码
public class Perm {
public static void main(String[] args) {
//返回虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory(); //byte单位
//返回Jvm初始化总内存
long totalMemory = Runtime.getRuntime().totalMemory();
/**
* 默认情况下,分配的总内存是电脑内存的四分之一
* 初始化内存是六十四分之一
*配置堆的大小
* -Xms1024m -Xmx1024m -XX:+PrintGCDetails
*/
/**
* OOM:
* 尝试扩大内存查看结果
* 分析内存,看下哪里出现了问题
*/
System.out.println("MAX:" + max + " byte " + (max/(double)1024/1024) + "MB" );
System.out.println("totalMemory:" + totalMemory + " byte " + (totalMemory/(double)1024/1024) + "MB" );
}
}

(699392 + 305664) / 1024 = 981.5M
11. 内存快照jprofile
分析dump文件,快速定位内存泄漏
获得堆中的对象
import java.util.ArrayList;
/**
* dump:
* -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
* 系统出现oom的时候会Heap的信息Dump下来
*/
public class JProfile {
byte[] array = new byte[1*1024*1024];
public static void main(String[] args) {
ArrayList<JProfile> arrayList = new ArrayList<JProfile>();
int count = 0;
try {
while (true){
arrayList.add(new JProfile());
count++;
}
} catch (OutOfMemoryError o) {
o.printStackTrace();
}
}
}

12. GC
JVM在GC的时,并不是对这三个区域统一回收,大部分的时候都是在新生代
- 新生代
- 幸存区(from,to)
- 老年区
GC的两种类型
- 轻GC
- Full GC
GC的常用算法
引用计数器
-

-
计数器本身也会有消耗,每个对象都会有自己的一块空间存放计数
复制算法(重点)
- 谁空谁是to
- 每次GC都会将Eden园区活这的对象移发到幸存区中,一旦Eden区被GC后就会是空的
- 默认当一个对象经历15次GC都还没有死就会进入养老区 这个参数可以自定义

标记清除算法
缺点:
两次扫描,严重浪费时间,会产生内存碎片
优点:
不会需要额外的空间
标记压缩算法
- 对标记清楚算法的优化

13. 总结
内存效率 :复制算法----->标签清除------>标记压缩 (时间复杂度)
内存整齐度 :复制算法==标记压缩------>标签清除
内存利用率 :标记压缩==标签清除------>复制算法
GC :分代收集算法
年轻代: 存活率低, 复制算法 效率最高
老年代: 存活率高 标签清除 标记压缩 混合实现 ,大型项目慢慢调


浙公网安备 33010602011771号