JVM初探

JVM的位置

在这里插入图片描述

JVM体系架构

在这里插入图片描述

类加载器

  1. 类加载器收到类加载的请求
  2. 将这个请求委托给父类加载器,一直向上委托,直到启动类加载器
  3. 启动类加载器检查是否能加载这个类,能加载就结束,使用当前的加载器,否则就抛出异常,通知子加载器进行加载
  4. 重复步骤3
  5. 若都没找到抛出ClassNotFind异常
    在这里插入图片描述

双亲委派机制:保护安全 (APP -> EXC -> BOOT)
eg.如自己创建了一个和jdk库中类名相同的类,会一直向上找,最终在最上层运行

Native

凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层C语言的库!
会进入本地方法栈,调用本地方法接口(JNI

例如:new Tread().start() 源码中的 start0()方法。
private native void start0();

使用场景:Java驱动打印机,主要用于调用硬件,一般用不到

程序计数器(PC计数器)

一个线程一个虚拟机栈,每个线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址,也即将要执行的指令代码),占内存非常小可以忽略不计

方法区

  • 方法区是被所有线程共享
  • 静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池在方法区中,但是实例变量存在堆中。static、final、Class、常量池

虚拟机栈又叫栈内存,主管程序的运行,生命周期和线程同步
一个线程一个虚拟机栈,线程结束,栈内存也就释放,对于栈来说不存在垃圾回收问题
主要存放 8大基本类型 + 对象引用地址 + 实例的方法

栈运行原理:栈帧
栈满了:StackOverflowError(递归循环调用)

栈、堆、方法区的调用关系:
在这里插入图片描述

Heap。一个JVM只有一个堆内存,堆内存的大小是可以调节的。

  • 类加载器读取了类文件后,一般会把类、方法、常量、变量、保存我们所有引用类型的真是对象放在堆中
    在这里插入图片描述

JDK8以前,堆中分为三个区域

  • 新生区

    • 类:诞生和成长的地方,甚至死亡
    • 所有的对象都是在伊甸园区被new出来的
    • 幸存者区(有0和1区)
  • 老年区

    • 经过GC之后仍然存活的对象
  • 永久区
    这个区域常驻内存,用来存放JDK自身携带的Class对象、Interface元数据,存储的是Java运行时的环境。这个区域不存在垃圾回收。关闭虚拟机就会释放这个区域的内存。
    以下情况会出现OOM:大量动态生成的反射类不断被加载、Tomcat部署了太多应用、一个启动类加载了大量第三方Jar包

    • jdk1.6之前:永久代,常量池在方法区
    • jdk1.7:永久代,常量池在堆中
    • jdk1.8之后:元空间,常量池在元空间中。

GC垃圾回收主要在新生区老年区

  • 新生区中的伊甸园区满了之后会发生轻量级GC
    • 轻GC一次后未回收的对象在幸存区,达到15或20次之后到达养老区
  • 老年区满了之后会发生重量级GC(Full GC)

假设内存满了,会报错OOM。(例如String一直+=)

在这里插入图片描述
堆内存OOM解决:

  1. 尝试扩大堆内存看结果:在Edit Configuration界面VM options参数中填写: -Xms1024m -Xmx1024m。(默认情况下分配的总内存是电脑内存的1/4,而初始化的内存是1/64)
  2. 分析内存,看哪个地方出现了问题(一般是内存泄露)
  3. 使用JProfiler

新生区的内存大小+老年区的内存大小=总内存大小,说明了元空间逻辑上存在物理上不存在。

使用JProfiler工具分析OOM原因

idea下载JProfiler插件,百度下载JProfiler软件

在Edit Configuration界面VM options参数中填写:
-Xms1m -Xmx8m -Xx:+HeapDumpOnOutOfMemoryError)
其中(OutOfMemoryError)可替换,-Xms设置初始化内存分配大小, -Xmx设置最大分配内存,-Xx:+PrintGCDetails打印GC垃圾回收具体信息,-Xx:+HeapDumpOnOutOfMemoryError打印OOM Dump信息

运行后会在src同级目录自动生成 .hprof文件,双击打开后可在Biggest Objects中查看哪些类占用较大
在这里插入图片描述
在左侧Heap walker中可查看哪行代码出了问题
在这里插入图片描述

GC垃圾回收算法

只在堆中进行GC,

  1. 引用计数法(基本不会使用)
    在这里插入图片描述
  2. 复制算法

from和to区是随时变换的,但to区一定是空的。
将幸存区from中的对象全部移动到to中。
在这里插入图片描述
GC清理一次过程

  • 好处:没有内存的碎片
  • 坏处:浪费了内存空间:永远都会有一块是空的(to区)。当from全部存活时,复制算法将会造成非常大的浪费。
  • 使用场景:对象存活度较低的时候(新生区)
  1. 标记清除算法
    在这里插入图片描述
    缺点:两次扫描,严重浪费时间,会产生内存碎片
    有点:不需要额外空间

  2. 标记整理(标记压缩)

在标记清除的步骤中多加了一步扫描:整理

在这里插入图片描述

  1. 标记清除整理(标记清除压缩算法)

在标记清除5次后,再进行整理

总结:

内存效率(时间复杂度):复制算法 > 标记清除算法 > 标记整理(压缩)算法
内存整齐度:复制算法 = 标记整理(压缩)算法 > 标记清除算法
内存利用率:标记清除算法 = 标记整理(压缩)算法 > 复制算法

没有最优的算法,只有最合适的算法------>GC分代收集算法

GC分代收集算法:
年轻代:存活率低,使用复制算法
老年代:区域大、存活率高:标记清楚(内存碎片不是太多) + 标记整理混合实现

在这里插入图片描述

轻GC(Minor GC)和重GC(Full GC)

Minor GC

当新对象去伊甸园区(Eden)申请内存失败的时候,就会进行Minor GC,对伊甸园区(Eden)回收非存活对象,而没有被回收的对象,会进入幸存区(Survivor),这种GC只发生在伊甸园区(Eden),不会影响到老年区。因为新对象分配内存大部分都在伊甸园区(Eden),所以伊甸园区(Eden)GC比较频繁。

注意:在GC之后,还存活的对象,进入幸存区(Survivor),谁空谁是to,可以交换位置,当一个对象经历了15次GC(可以配置次数:-XX:+MaxTenuringThreshold=15),还存活,就进入老年区。

Full GC

清理整个堆,因为Full GC需要对整个堆进行回收,所以比Minor GC慢,因为我们要尽可能的减少Full GC的次数。我们所说的JVM调优,很大一部分就是对Full GC的优化。

以下情况会造成 Full GC:

老年区满了:年轻区的对象转入或创建大对象才会满。
持久区满了(jdk7及之前版本)
方法区满了(jdk8及之后版本):系统中要加载的类过多。
System.gc() 被显示调用
通过Minor GC后进入老年代的平均大小大于老年代的可用内存:第一次Minor GC之后,有2MB的对象转入老年区,然后在下一次Minor GC的时候就会判断老年区的空间是否有2MB,如果没有就进行Full GC。

拓展

JMM

  1. 什么是JMM
    Java Memory Model :Java内存模型
  2. 它干嘛的?
    缓存一致性协议,用于定义数据读写的规则(遵守)
    JMM中定义了线程工作内存与主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)

在这里插入图片描述
解决共享对象可见性的问题:volatile

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值