JVM虚拟机
JVM位置
JVM体系结构
类加载器
作用:加载class文件
1.虚拟机自带的加载器
2.启动类(根)加载器
3.扩展类加载器
4.应用程序加载器
寻找:4 => 3 => 2 => 1
双亲委派机制
当某个类加载器需要加载某个.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
类加载器的类别
- BootstrapClassLoader(启动类加载器)
c++
编写,加载java
核心库 java.*
,构造ExtClassLoader
和AppClassLoader
。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
- ExtClassLoader (标准扩展类加载器)
java
编写,加载扩展库,如classpath
中的jre
,javax.*
或者java.ext.dir
指定位置中的类,开发者可以直接使用标准扩展类加载器。
- AppClassLoader(系统类加载器)
java`编写,加载程序所在的目录,如`user.dir`所在的位置的`class
- CustomClassLoader(用户自定义类加载器)
java
编写,用户自定义的类加载器,可加载指定路径的class
文件
- 源码分析
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先检查这个classsh是否已经加载过了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// c==null表示没有加载,如果有父类的加载器则让父类加载器加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果父类的加载器为空 则说明递归到bootStrapClassloader了
//bootStrapClassloader比较特殊无法通过get获取
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {}
if (c == null) {
//如果bootstrapClassLoader 仍然没有加载过,则递归回来,尝试自己去加载class
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
1.类加载器收到类加载的请求
2.讲这个请求向上委托给父类加载器完成,一直向上委托,直到启动类加载器
3.启动类加载皮检查是否能够加载这个类,能加载就结束,使用当前加载器,否则抛出异常,通知子加载器进行加载
4.重复步骤3
Native
public static void main(String[] args) {
new Thread(()->{
},"t1").start();
}
//native:凡是带了native 关键字的方法,说明java的作用范围达不到,会去调底层的C语言的库
//进入本地方法栈
//调用本地接口 JNI
//JNI作用:扩展java,融合不同语言
//他在内存区域中专门开辟一块标记区域 Native Method Stack 等级native方法
//在最终执行的时候,通过 JNI 加载本地方法库中的方法
方法区:所有线程共享,所有定义方法的信息都在该区域。
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是 实例变量存在堆内存中,和方法区无关。(static、final、Class、常量池)
堆
一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中?类,方法,常量,变量~,保存我们所有引用类型的真实对象;
堆内存中还要细分为三个区域:
- 新生区(伊甸园区)
- 养老区
- 永久区
JDK1.8之前
GC 垃圾回收主要在伊甸园区和养老区,假设内存满了,出现OOM,对内存不够。
新生区
- 类:诞生和成长的地方,甚至死亡
- 伊甸园区:所有的对象都是在伊甸园区new出来的
- 幸存者区 0 1
真相:99%的对象都是临时对象。
老年区
永久区
这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭VM虚拟就会释放这个区域的内存
一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态生成的反射类。不断的被加载。直到内存满,就会出现OOM;
- jdk1.6之前:永久代,常量池是在方法区;
- jdk1.7 :永久代,但是慢慢的退化了,去永久代,常量池在堆中
- jdk1.8之后:无永久代,常量池在元空间
元空间:在堆的概念上,逻辑上存在,物理上不存在
public class Jvm03 {
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("max=" + total + "字节\t" +(total/(double)1024/1024)+"MB");
//默认情况下,分配的总内存是电脑的1/4,初始化内存是1/64
//-Xms1024m -Xmx1024m -XX:+PrintGCDetails
//OOM:
//1.尝试扩大内存看结果
//2.分析内存,看一下哪个地方吃西安了问题(使用工具)
/* max=1029177344字节 981.5MB
max=1029177344字节 981.5MB
Heap
PSYoungGen total 305664K, used 20971K
eden space 262144K, 8% used
from space 43520K, 0% used
to space 43520K, 0% used
ParOldGen total 699392K, used 0K
object space 699392K, 0% used
Metaspace used 3450K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
*/
//305664K + 699392K = 1005056K = 981.5M
}
}
public class Jvm02 {
public static void main(String[] args) {
String str = "hiohoihoihiohiohoihoihoihoihoihiohoihoihoihoih";
while (true){
str = str + new Random().nextInt(888888888) + new Random().nextInt(999999999);
}
}
//Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
}
使用 JProfiler 工具分析OOM原因,Runtime就是调优的类
Java 对象在内存中的实例化过程
在类的实例化过程中,内存会使用到的三个区域:栈区、堆区、方法区。
- 堆区:
- 存储的全部都是对象,每个对象包含了一个与之对应的 class 类的信息。
- jvm 只有一个堆区(steap),它会被所有线程共享,堆中不存放基本数据类型和对象引用,它只存放对象本身。
- 栈区:
- 每个线程都包含一个栈区,栈中只保存基本数据类型的值和对象以及基础数据的引用。
- 每个栈中的数据(基本数据类型和对象的引用)都是私有的,其它栈是无法进行访问的。
- 栈分为三个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
- 方法区:
- 又被称为静态区,它跟堆一样,被所有的线程共享,方法区包含所有的 class 信息 和 static修饰的变量。
- 方法区中包含的都是整个程序中永远唯一的元素,如:class、static变量。
垃圾回收(GC)
JVM在进行GC时,并不是对这三个区域统一回收。大部分时候。回收都是新生代。
- 新生代
- 伊甸园区
- 幸存区(form , to)
- 老年区
GC两种类:轻GC(普通的GC) ,重GC(全局GC)
复制算法(新生代主要)
谁空谁是 to from复制到to,保证to永远是空的,减少内存中产生碎片,经过十五次复制,还没有死亡,进入老年区
- 好处:没有内存碎片
- 坏处:浪费内存空间,假设对象100%存活,适用于对象存活度不高的场景。
标记清除算法
- 优点:不需要额外空间
- 缺点:两次扫描,浪费时间,会产生内存碎片
标记压缩算法
引用计数器
总结
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
GC:分代收集算法
新生代:复制算法,存活率低
老年代:标记清除+标记压缩 (内存碎片不是太多)
JMM
1.什么是JMM?
JMM(Java内存模型)本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
2.JMM是干什么的?
作用:缓存一致性协议。用于定义数据读写的规则(遵守)
JMM定义了线程工作和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)
可见性问题:
解决共享对象可见性这个问题: volilate
3.怎么学习?
volilate:解决问题
JMM是一种抽象的概念,并发问题。