1.JVM的位置
1.1.哪些地方不会有垃圾回收呢?
2.类加载器
作用:加载class文件
图解:实例化对象过程
2.1.类加载器
2.2.双亲委派机制
2.3.为什么使用双亲委派机制
2.4.类装载的执行过程
2.4.1.加载
2.4.2.连接
2.4.2.1.验证
2.4.2.2.准备
2.4.2.3.验证
2.4.3.初始化
2.4.4.使用
2.4.5.卸载
3.Native、方法区
native
凡是native修饰的方法,说明java的作用范围达不到,回去调用 C语言的库,会进入本地方法栈
方法区(重要)
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,比如构造函数,接口代码也在此定义。简单来说,所有定义方法的信息都保存在该区域,次区域属于共享区间
静态变量、常量、类信息(构造方法,类定义)、运行时的常量池保存在方法区中。但是实例变量存在堆内存,和方法区无关
方法区就存这些东西:static、final,class,常量池
4.栈
栈:先进后出,后进先出:主管程序的运行,生命周期和线程结束,线程结束,栈内存就释放了,对于栈来说不存在垃圾回收。
队列:先进先出,后进后出
为什么main方法先调用后执行?
栈溢出概念
栈执行原理:栈帧
堆、栈、方法区交互关系
图解:代码new了class(对象)后,会放到栈内存,赋值后,实际数据是存放在堆内存的。通过栈的地址值去堆中找到具体的指,堆中的常量是存放在方法区中的常量池的
对象实例化过程
1.堆内存中存放对象的属性,栈内存中存放的是引用变量
2.堆内存中为new Person()开辟空间的地址BE2500,是栈内存中引用变量p的值
子类对象的实例化过程
1.因为先开辟子类的内存空间,故先使子类构造方法进栈
2.因为子类的构造方法中使用了super关键字,要执行父类的构造方法,所以就要在执行父类的构 造方法之前,先显示初始化父类的属性
3.执行完父类构造方法后出栈,再显示初始化子类的属性
5.堆
HotSpot:一个jvm只有一个堆,大小是可以调节的
类加载器读取一个文件后会把什么存放在堆里呢?
类、方法、变量、常量,保存我们所有引用类型真实变量。
三个区域
新生区(伊甸园区)、老年区、永久区
GC垃圾回收主要是在伊甸园去和老年区(新生区主要是轻量级GC,老年代主要是重量级)
在jdk1.8以后,永久存储区叫做元空间,不在jvm中,而是在本地内存中
新生区
类的创建和死亡的地方
伊甸园区
所有对象都是在这里new出来的,用完后如果gc未能回收,则进入幸存区
幸存区
分为幸存0区和幸存1区:当伊甸园区轻量gc未清理掉的垃圾会进入幸存区
养老区
新生区满了会进入此区域进行重GC
永久区
这个区域常住存在,用来存储一些jdk自带的class对象、元数据、java运行时的环境或者类型,这个区域不存在垃圾回收,当关闭jvm虚拟机,这个内存就会释放
jdk1.6:永久带,常量池在堆中
jdk1.7:永久带,慢慢退化,常量池在堆中
jdk1.8:无永久区,常量池在原空间
内存和调参
package com.cn.juc;
public class LazyMan {
public static void main(String[] args) {
//虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory();
//JVM总内存
long total = Runtime.getRuntime().totalMemory();
System.out.println("max=" + max + "字节\t" + (max/(double)1024/1024) +"MB");
System.out.println("total=" + total + "字节\t" + (total/(double)1024/1024) +"MB");
}
}
默认情况下:分配的总内存是电脑内存的1/4。而初始化的内存:1/64
OOM排查思路
1.尝试扩大内存
2.如果还是oom,就要分析内存,看下哪个地方出现了问题
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
-Xms:初始内存
-Xmx:总内存
-XX:+PrintGCDetails:打印GC的过程信息
package com.cn.juc;
public class LazyMan {
public static void main(String[] args) {
//虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory();
//JVM总内存
long total = Runtime.getRuntime().totalMemory();
System.out.println("max=" + max + "字节\t" + (max/(double)1024/1024) +"MB");
System.out.println("total=" + total + "字节\t" + (total/(double)1024/1024) +"MB");
}
}
计算可以知:新生带 + 老年带 = 总内存 -> 云空间逻辑存在,实际不存在堆内存
GC:垃圾回收机制
jvm在进行GC时,并不是对三个区域统一回收,大部分回收都是在新生带
类型:轻GC,重GC(fullGC)
引用计数法
图解:括号的下边为对象的使用次数,当次数为0证明未被使用,做gc处理
缺点:目前不用这种算法,效率不高,而且本身也占内存
复制算法
好处:没有内存碎片
坏处:浪费了内存空间:幸存to区空间永远是空。假设对象100%存活,复制算法就不合理
复制算法最佳使用场景:对象存活较低的时候。
标记清除算法
优点:不需要额外空间
缺点:两次扫描验证浪费时间,会产生内存碎片
标记压缩算法
GC总结
内存效率(时间复杂度):复制算法>标记清除算法>标记压缩算法
内存整齐度:复制算法=标记压缩算法>标记清楚算法
内存利用率:标记压缩算法=标记清除算法>复制算法
没有最好的算法,只有最合适的算法
GC一般采用分代收集算法
年轻代:对象存活率,一般采用复制算法
老年代:存活率高,区域大。一般采用标记清除(内存碎片不是太多)+标记压缩混合实现
JMM(java内存模型)
它描述的是和多线程相关的一组规范,需要各个 JVM 的实现来遵守 JMM 规范,以便于开发者可以利用这些规范,更方便地开发多线程程序。这样一来,即便同一个程序在不同的虚拟机上运行,得到的程序结果也是一致的。(缓存一致性协议,用于定义数据读写的规则)