本篇文章为大家展示了怎么进行JVM原理入门,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
类装载器ClassLoader
负责加载class文件,class文件在文件开头有特定的文件标识,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构并且ClassLoader只负责class文件的加载,至于它是否运行,则是由Execution Engine决定 (快递员)
1、虚拟机自带的加载器:
启动类加载器(Bootstrap)C++ :加载jdk原始自带的类,在jdk1.8.0_201\jre\lib\rt.jar下,如Object、String、ArrayList等 但是如果要用getClassLoader获取加载器名的话会显示null,因为该加载器是C++写的不是Java的
扩展类加载器(Extension)Java:加载jdk升级过程中扩展的类,在jdk1.8.0_201\jre\lib\ext目录下
应用程序类加载器(AppClassLoader)java也叫系统类加载器,加载当前 应用的classpath的所有类
2、用户自定义加载器
Java.lang.ClassLoader的子类,用户可以定制的加载方式
3、常见面试问题
双亲委派机制:
(1)如果一个类加载器收到了类加载请求,不自己加载而是把请求给其父类去执行.
(2)若父类加载器还存在其父类的加载器,进一步向上委托.
(3)如果父类加载器可以完成类加载任务就返回,若不能才让自己子类加载
沙箱安全:双亲委派机制保证了Java的出厂源码不会受到开发人员编写的污染(沙箱安全机制)
本地方法接口
一个Native Method就是一个Java调用非Java代码的接口,该方法是由非Java语言实现。
有时Java应用需要与Java外面的环境交互,这是本地方法存在的主要原因。你可以想想Java需要与一些底层系统,如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解Java应用之外的繁琐的细节。
PC寄存器(程序计数器)
每一个线程都有一个程序计数器,是线程私有的,就是一个指针,用来存储指向下一条指令的地址,也就是将要执行的指令代码。是一块儿非常小的内存,它是当前线程所要执行的字节码的行号指示器。如果执行的是一个Native方法,那么这个计数器是空的。
方法区
它存储了每个类的结构信息,例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容。
上面说的是规范,在不同的虚拟机里实现是不一样的。最经典的就是永久代和元空间。
实例变量存在堆内存中,与方法区无关
stack(java栈)
栈又叫栈内存,主管java程序的运行,是在线程创建时创建,他的生命周期是跟随线程的生命周期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,生命周期和线程一致,是线程私有的。8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配的。
5.1栈帧(在外面就是方法)中主要保存3类数据:
本地变量:输入参数和输出参数以及方法内的变量;
栈操作:记录出栈、入栈的操作;
栈帧数据:包括类文件、方法等等。
5.2java.lang.StackOverflowError (栈溢出)他是一个错误,是一个error
5.3 栈+堆+方法区的交互关系
HotSpot(一般咱们的jdk都是这个版本)是使用指针的方式来访问对象。
Java堆中会存放访问类元数据的地址
reference存储的就直接是对象的地址
heap堆
一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。
先看下JAVA堆内存是如何划分的,如图:
(1)JVM内存简单可以划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存有永久代(Permanent Generation),jdk1.8及之后元空间取代永久代。
(2)年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
(3)堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
(4)非堆内存用途:永久代,一般hotspot下也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。
重点:
(1)首先当Eden区满的时候会触发第一次GC,把活着的对象拷贝到SurvivorFrom区
然后当Eden区再次触发GC的时候会扫描Eden+From区,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1
(2)清空Eden、SurvivorFrom
然后,清空Eden和SurvivorFrom中的对象,也即复制之后有交换,谁空谁是to
Old(养老区)满了,开启
Full GC = FGC (重GC)
如果Full GC多次,发现老年代也满了,就会报 OOM(堆内存溢出)
元空间与永久代
Jdk1.8以后开始把类的元数据放在元空间(Metaspace)中,该区域在jdk7及以前是属于永久带的,元空间和永久代都是用来存储class相关信息,包括class对象的Method,Field等,元空间和永久代其实都是方法区的实现,只是实现有所不同,所以说方法区其实只是一种JVM的规范。
两者最大的区别是元空间使用本地内存,而永久代使用的是JVM的内存,也就是说,本地内存剩余多少理论上metaspace就可以有多大,这解决了空间不足的问题,不过也不可能任其无限壮大,JVM默认在运行时会根据需要动态的设置其大小。
GC是什么(分代收集算法)
1、次数上频繁收集Young区
2、次数上较少收集Old区
3、基本不动Perm区
JVM在进行GC时,大部分时候回收的都是新生代。
因此GC按照回收的区域分了两种类型,一种是普通的GC(minor GC),一种是全局GC(minor GC or Full GC)
针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:
1.Partial GC:并不收集整个GC堆的模式,具体如下:
Young GC/Minor GC:只收集新生代的GC。
Old GC:只收集老年代的GC。只有CMS的concurrent collection是这个模式。
Mixed GC:收集整个新生代以及部分老年代的GC,只有G1有这个模式。
2.Full GC/Major GC:收集整个GC堆的模式,包括新生代、老年代、永久代(如果存在的话)等所有部分的模式。
常见垃圾收集算法
标记 - 清除算法
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
复制算法
为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。
标记 - 整理算法
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
上述内容就是怎么进行JVM原理入门,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。