您好,欢迎来到刀刀网。
搜索
您的当前位置:首页JVM相关

JVM相关

来源:刀刀网

1.JVM内存区域

一个运行起来的java进程就是一个Java虚拟机,就需要从操作系统中申请一大块内存。

内存中会根据作用的不同被划分成不同的区域:

(1)栈:存储的内容是代码在执行过程中,方法之间的调用关系(栈中每一个元素就是一个栈帧,代表一个方法的调用,包含方法的形参,返回值,局部变量)。

(2)堆:这里存储的内容是代码中new的对象

(3)方法区(1.8开始:元数据区):存储类对象(.class文件加载到内存就成为了类对象)

程序计数器中的值会随着指令的执行,从而自动进行更新,去指向下一条指令。

ps:栈和程序计数器每一个java线程一份,而堆和方法区是每一个java进程一份。(仅限java,放在c++中就不一定是这样了)

2.JVM类加载

(1)基本流程:

java代码经过编译后生成.class文件,java程序要想运行起来就需要将jvm读取这个.class文件,并且把里面的内容构造成类对象,保存到内存的方法区中。

官方文档中把类加载的过程分成了5个步骤:

1)加载:找到.class文件,并且打开文件,读取文件中的内容(通过全限定名称查找)

2)验证:检查当前二进制文件是否符合格式的要求,

3)准备:给类对象分配内存空间,并且为不被final修饰的static变量赋初始默认值(被final修饰的变量在此时会被赋值成程序员给定的值)

4)解析:针对类对象中字符串常量的处理,进行了一些初始化的操作。(符号引用到直接引用)

5)初始化:对类对象中的各种属性进行初始化,还需执行静态代码块和加载一下父类(子类中调用构造方法,会先帮助父类进行构造)

(2)双亲委派模型:

是在加载过程中的第一个步骤。

首先需要了解一下类加载器

是JVM中的一个模块,JVM内置了三个类加载器:

1)BootStrap ClassLoader

2)Extension ClassLoader

3)Application ClassLoader

这三个类加载器之间的关系就好似爷父子之间的关系,但不是靠继承构成的,而是由类加载中的一个属性(parent)来指向父类加载器。

加载的具体流程是:

①根据给定的全限定类名,找到对应的.class文件。

②从Application ClassLoader作为入口,开始执行查找的逻辑。

⑥如果在Extension ClassLoader扫描到了就结束加载过程,没有扫描到就交给它的子类加载器(Application ClassLoader)扫描。

⑦如果在Application ClassLoader扫描到了就结束加载过程,没有扫描到此时应该交给它的子类加载器扫描,但是发现没有了,所以此时会抛出一个异常ClassNotFoundException

 3.JVM垃圾回收机制(GC垃圾回收)

在java中new对象,是动态申请内存(运行时分配),如果一个资源申请了内存空间,长时间不使用但是不释放,就可能会造成内存泄漏。

在java中给出了一个方案,也就是垃圾回收机制,让JVM,自动判定某个内存是否不再使用了,

如果后面这个内存确实不用了,JVM就会自动回收把这个内存给回收掉了,此时就不需要手动回收了。

GC是垃圾回收,GC回收的目标是内存中的对象。对于java来说就是释放堆上的new出的对象,栈上的对象是随着栈帧的生命周期(方法执行结束,栈帧自然销毁,内存自然释放),静态变量,生命周期是整个程序,这个始终存在就意味着静态变量无需释放的,真正释放的就是堆上的对象。

GC回收垃圾的过程主要有两个步骤:

(1)找到垃圾

有两种主流的方案:

①引用计数

new出来的对象,会单独划分空间,来保存有一个计数器,计的是当前有多少引用指向该对象。

如果一个对象没有引用指向了(即引用计数为0),就可以将该对象视为垃圾了。

但是使用该种方式可能会存在两种问题:

·比较浪费内存:

计数器怎么说也得有两个字节,如果对象本身比较小,那么此时这个计数器所占空间比例就比较大了。

·存在循环引用问题:

②可达性分析:

有一组线程,周期性扫描代码中的所有对象,把所有可以访问到的对象,都给标记成“可达”,反之,如果经过扫描后,不可达的对象,就成垃圾了,需要被回收。

eg:

当然这里的遍历不一定是二叉搜索树,这里可达的实现大概率是靠N叉搜索树实现,这一步就是针对当前对象,看对象中有多少不同引用类型的成员,然后再对每一个类型的成员进行进一步的遍历。

通过上述过程,不难看出可达性分析是比较耗费资源的(开销较大)。 

(2)回收垃圾

有三种基本的回收思路:

①标记删除:

扫描到一个不可达的对象,就直接进行释放,这个方案非常不好,那就是会产生很多的内存碎片。释放代码,是为了让其他代码能够申请一块连续的内存空间,随着时间的推移,内存碎片的情况就会愈演愈烈,就会导致后续申请连续内存空间变得困难。

②复制算法:

通过复制的方式,把有效的对象,归类到一起,在同一释放剩下的对象。

也就是把内存一分为二,一次只用其中一半。但这种方式有两个明显的问题:

a.内存利用率不高

b.如果有效的对象很多,那么复制的开销将会很大

③标记整理:

既能解决内存碎片的问题,又能够解决上述内存利用率不高的问题。

eg:

类似于顺序表删除元素的搬运操作,使用该种方式搬运开销仍然很大。

而JVM中GC垃圾回收主要思想是分代回收(具体实现可能有一些调整和优化),是对上述思路的集合,让不同的方案,扬长避短。

分代回收有一个很重要机制就是:对象能活过的GC扫描轮次越多,就是越老(代表当前对象是暂时不会被释放的)

eg:下图是整个分代回收的全部过程:

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- gamedaodao.com 版权所有 湘ICP备2022005869号-6

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务