初步了解JVM

JVM

整体组成部分

类加载器

类加载过程

加载:使用IO读取字节码文件,转换并存储,为每个类创建一个Class对象,存储在方法区中

链接(验证,准备,解析)

​ 验证:对字节码文件格式进行验证,文件是否被污染,对基本的语法格式进行验证

​ 准备:为静态的变量进行内存分配,赋值为默认值(静态的常量在编译期间就进行初始化)

​ 解析:将符号引用转为直接引用,将字节码中的表现形式转为内存中表现(内存地址)

初始化

类的初始化,为类中的定义静态变量进行赋值

类在什么时候会被初始化(加载)

1.在类中运行main方法

2.创建对象

3.使用类中静态常量、静态方法

4.使用反射

5.初始化它的子类

以下两种情况不会被初始化:

1.使用编译期间被赋值的静态常量

2.作为数组类型

作用

负责从硬盘/网络中加载字节码信息,加载到内存中(运行时数据区的方法区中)

启动类加载器

Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。是由c/c++编写的,与Java语言无关

扩展类加载器

ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。

是由Java语言编写的

应用程序类加载器

AppClassLoader:主要负责加载程序中自己开发的类

public class ClassLosderTest {
    public static void main(String[] args) {
        //启动类加载器
        ClassLoader a = new String("a").getClass().getClassLoader();
        System.out.println(a);
        ClassLoader classLoader = new ClassLosderTest().getClass().getClassLoader();
        //应用程序类加载器sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(classLoader);
        //扩展类加载器 sun.misc.Launcher$ExtClassLoader@1b6d3586
        System.out.println(classLoader.getParent());
        System.out.println(classLoader.getParent().getParent());
    }
}

测试结果:

null
sun.misc.Launcher A p p C l a s s L o a d e r @ 18 b 4 a a c 2 s u n . m i s c . L a u n c h e r AppClassLoader@18b4aac2 sun.misc.Launcher AppClassLoader@18b4aac2sun.misc.LauncherExtClassLoader@1b6d3586
null

运行时数据区

程序计数器是不会产生内存溢出的

堆和方法区是线程共享的,是会出现垃圾回收的

程序计数器,本地方法栈,Java栈是线程私有的

堆,方法区,本地方法栈,Java栈是可以进行内存大小设置的

堆,方法区,本地方法栈,Java栈是会出现内存溢出的

Java栈(stack)
基本作用特征:

栈是运行单位,用来管理方法的调用运行,是用来运行Java方法的区域,可能会出现栈溢出,是线程私有的

运行原理:

是先进先出的结构,最顶部的称为当前栈帧

栈帧结构:

局部变量表(存储方法中声明的变量)

操作数栈(实际计算运行)

动态链接

方法返回地址

堆(heap)
基本作用特征

一个jvm只有一个堆,是线程共享的,堆内存的大小是可以调节的,是用来存储对象的,是内存空间最大的区域,本区域是存在垃圾回收的

堆空间的分区

新生区

(伊甸园区,幸存0区,幸存1区)

永久区

为什么要分区

针对于不同的区,可以使用不同的垃圾回收算法,频繁回收新生代,较少回收老年代

创建对象,垃圾回收的过程

1.新创建的对象,都存储在伊甸园区

2.当垃圾回收时,将伊甸园区中的垃圾对象直接销毁,将幸存下来的对象转移到幸存0区

3.当再次进行垃圾回收时,将伊甸园区的幸存对象和幸存0区中的对象转移到幸存一区,每次保证一个幸存者区为空

4.每次垃圾回收时,幸存下来的对象都会记录幸存次数,当一个对象经历15次垃圾回收仍然幸存下来就会被移动到老年区

5.老年区垃圾回收次数较少,当内存空间不够用时,才会进行回收老年区。

堆空间的配置比例

新生区和老年区内存占比默认为1:2

伊甸园区:幸存0区:幸存1区=8:1:1(默认)

分代收集思想

Minnor GC 新生区收集,经常发生

Major GC 老年区收集,较少发生

整堆收集 对整个堆以及方法区进行收集(发生情况:老年区空间不足,方法区空间不足,System.gc())

字符串常量池

在JDK7之前位于方法区,7之后位于堆空间中,因为方法区的收集是在整堆收集时发生,频率较低

方法区(method area)

静态变量,常量,类信息(构造方法,接口),运行时的常量池(类中各个元素的编号)

(static final Class 常量池)

特点

jvm启动时创建,大小可以调整,是线程共享的,会有内存溢出问题

方法区的垃圾回收

在full GC时发生,主要是回收类信息,发生的条件比较苛刻,满足以下三点即可:

1.在堆中该类的对象以及子类的对象不存在

2.该类的类加载器不存在

3.该类的Class对象不存在

程序计数器(program counter register)

每个线程都有一个程序计数器,是线程私有的,是一个非常小的内存空间,几乎可以忽略不计,是用来记录每个线程运行的指令位置,是运行时数据区唯一不会内存溢出的一块空间,运行速度最快

本地方法栈(native method stack)

每个线程私有,用来运行本地方法的区域,空间大小可以调整,可能会出现栈溢出

执行引擎

作用

将加载到内存的字节码编译/解释为不同平台的机器码
.java文件----编译—>.class文件 是在开发期间,由jdk提供的编译器(javac)进行源码编译(前端编译)

.class文件----解释/编译—>机器码 后端编译,在运行时,由执行引擎完成

解释器和编译器结合

解释器:将字节码逐行进行解释执行,效率低

编译器(JIT):将字节码编译,并且在方法区中缓存起来,执行更高效,不会立即使用编译器(将一些频繁执行的热点代码进行编译)

程序启动后,先使用解释器立即执行,省去了编译时间

程序运行一段时间后,对热点代码进行编译缓存,为了后续提高效率

本地方法接口

什么是本地方法

凡是带了native关键字的,说明Java的作用范围达不到,会去调用本地方法接口,没有方法体(例如 hashCode() new thread().start())

为什么要用本地方法

JAVA语言需要和外部的环境进行交互(例如需要访问内存硬盘其他硬件设备),直接访问操作系统的接口即可

jvm中的一些机制

双亲委派机制

内容

当需要记载一个类时,当前加载器不会先进行加载,会向上委托父类加载器进行加载,一直到启动类加载器,如果启动类加载器无法加载,就会通知子类进行加载,一直到应用程序类加载器,最终如果都没有找到,就会抛出classnotfound异常

优点

安全性较高,防止核心类库被修改,避免了类的重复加载

如何打破双亲委派机制

创建自己的类加载器继承ClassLoader类,并且重写里面的loadClass/findClass方法。

例如:Tomcat使用自己的类加载器来加载类

垃圾回收

什么样的对象是垃圾

在运行过程中,没有被任何引用指向的对象成为垃圾对象

为什么需要GC

如果不及时清理垃圾的话,可能会出现内存溢出问题,在回收时还可以将内存碎片进行整理

内存溢出和内存泄漏

内存溢出:经过垃圾回收后,还是无法存储新创建的对象,内存不够用就是内存溢出

内存泄漏:一些不用的对象但是jvm无法判定为垃圾,不能进行垃圾回收,默默占用内存这样的叫内存泄漏(IO流close jdbc close)

垃圾回收算法

标记阶段

作用: 判断对象是否是垃圾对象, 是否有引用指向对象.

相关的标记算法: :引用计数算法和可达性分析算法

引用计数算法(在现代的jvm中并没有被使用).

有个计数器来记录对象的引用数量

String s1 = new String("aaa");
String s2 = s1;  //有两个引用变量指向aaa对象
s2 = null; -1
s1 = null; -1

缺点:

需要维护计数器,占用空间,频繁操作需要事件开销

无法解决循环引用问题. 多个对象之间相互引用,没有其他外部引用指向他们,计数器都不为0,不能回收,产生内存泄漏.

可达性分析算法/根搜索算法

实现思路: 从一些为根对象(GCRoots)的对象出发去查找,与根据对象直接或间接连接的对象就是存活对象,不与根对象引用链连接的对象就是垃圾对象.

GC Roots 可以是哪些元素?

在虚拟机栈中被使用的.

在方法中存储的静态成员指向的对象

作为同步锁使用的 synchronized

在虚拟机内部使用的对象

对象的 finalization 机制

当一个对象被标记为垃圾后,在真正被回收之前,会调用一次Object类中finalize(). 是否还有逻辑需要进行处理.

自己不要在程序中调用finalize(),留给垃圾回收器调用.

有了finalization机制的存在,在虚拟机中把对象状态分为3种:

1.可触及的 不是垃圾,与根对象连接的

2.可复活的 判定为垃圾了,但是还没有调用finalize(),(在finalize()中对象可能会复活)

3.不可触及的: 判定为垃圾了,finalize()也被执行过了,这种就是必须被回收的对象

垃圾回收阶段的算法

标记–复制算法:

将内存分为大小相等的两份空间, 把当前使用的空间中存活的对象 复制到另一个空间中, 将正在使用的空间中垃圾对象清除.

优点: 减少内存碎片

缺点: 如果需要复制的对象数量多,效率低.

适用场景: 存活对象少 新生代适合使用标记复制算法

标记-清除算法

清除不是真正的把垃圾对象清除掉,

将垃圾对象地址维护到一个空闲列表中,后面有新对象到来时,覆盖掉垃圾对象即可.

特点:

​ 实现简单

​ 效率低,回收后有碎片产生

标记-压缩算法(标记-整理)

垃圾回收器

垃圾收集器是垃圾回收的实际实现者,垃圾回收算法是方法论.

垃圾回收器分类

按照线程数量

单线程垃圾回收器

Serial

Serial old

多线程垃圾回收器

Parallel

按照工作模式分为

独占式: 垃圾回收线程执行时,其他线程暂停

并行式: 垃圾回收线程可以和用户线程同时执行

按工作的内存区间

年轻代垃圾回收器

老年代垃圾回收器

垃圾回收器性能指标

暂停时间

吞吐量

回收的速度

占用内存大小

CMS垃圾回收器

Concurrent Mark Sweep 并发标记清除

支持垃圾回收线程与用户线程并发(同时)执行

初始标记: 独占式的暂停用户线程

并发标记: 垃圾回收线程与用户线程并发(同时)执行

重新标记: 独占式的暂停用户线程

并发清除: 垃圾回收线程与用户线程并发(同时)执行 进行垃圾对象的清除

优点: 可以作到并发收集

弊端: 使用标记清除算法,会产生内存碎片, 并发执行影响到用户线程,无法处理浮动垃圾

三色标记

​ 由于cms有并发执行过程,所以在标记垃圾对象时有不确定性.

​ 所以在标记时,将对象分为3种颜色(3种状态)

​ 黑色: 例如GCRoots 确定是存活的对象

​ 灰色: 在黑色对象中关联的对象,其中还有未扫描完的, 之后还需要再次进行扫描

​ 白色: 与黑色,灰色对象无关联的, 垃圾收集算法不可达的对象

标记过程:

1.先确立GCRoots, 把GCRoots标记为黑色

2.与GCRoots关联的对象标记为灰色

3.再次遍历灰色,灰色变为黑色,灰色下面有关联的对象,关联的对象变为灰色

4.最终保留黑色,灰色, 回收白色对象

可能会出现漏标,错标问题

G1(Garbage-First) 垃圾优先

将堆内存各个区又分成较小的多个区域, 对这些个区域进行监测,对某个区域中垃圾数量大的区域优先回收.也是并发收集的.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值