大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长!
未来村村长正推出一系列【Coding Again】文章,对之前学过的和没学过的知识重新进行整理,因为现在再回顾之前的文章,写得很乱,大概是因为自己当时也没有搞太明白相关的知识点,再出发即是一次对过去的知识扬弃的过程。该系列文章以java后端学习路线为轴进行推出,如果喜欢就一键三连吧!
JDK:用于支持Java程序开发的最小环境,其中包含java程序语言、java虚拟机、java类库。JDK包含JRE。
JRE:用于支持Java程序运行的标准环境,Java类库API中的Java SE API子集和java虚拟机的统称。JRE大部分都是 C 和 C++ 语言编写的。JRE包含JVM。
JVM:Java虚拟机,本质上就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。这就是“一次编译,多次运行”。
我们在IDE中编写.java程序后,通过javac编译器编译成字节码文化(.class),类加载器把字节码文件加载到内存中,将其放在运行时数据区的方法区内。
字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine)将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口来实现整个程序的功能。
下图中,方法区、堆、执行引擎、本地库接口属于线程共享的数据区,其余区域属于线程私有数据区。
new:判断类是否加载、解析、初始化,若没有则执行类加载过程。
内存分配:判断内存是否规整,不规整使用空闲列表方法,规整使用指针碰撞方法。对于内存分配的并发问题,有两种方式处理方式,一是CAS同步处理,二是本地线程分配缓冲。
对象设置:将对象相关信息存入对象的对象头中
对象初始化:new指令后执行,
在HotSpot虚拟机中,对象在堆内存中的存储布局可以划分为三个部分:对象头、实例数据、对齐填充。
过程:Java堆中划分出一块内存放置访问类型数据的相关信息,reference中存储对象地址,这样访问对象就不需要间接访问。
优点:速度更快,节省了一次指针定位的时间开销,HotSpot主要以直接指针的方式进行对象访问。
过程:Java堆中划分出一块内存作为句柄池,reference中存储对象的句柄地址,句柄包含了对象实例数据与类型数据各自的地址信息。
优点:reference中存储的是稳定句柄地址,在对象被移动时,只需要改变句柄中的实例数据指针,reference本身不用改变。
根据内存溢出的区域可分为栈溢出、方法区和运行时常量池溢出、直接内存溢出。
溢出原因:只要我们不断地创建对象,并且垃圾回收机制未清理或不能及时收集清理,总容量触及最大堆的容量限制后,就会产生内存溢出异常。
解决:
溢出原因:HotSpot虚拟机不区分虚拟机栈和本地方法栈,当线程请求的栈深度大与虚拟机所允许的最大深度,将抛出SatckOverflowError异常。如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够内存时,将抛出OutOfMemoryError异常。
解决:
溢出原因:运行时常量池是方法区的一部分,运行时产生的类太多,如动态产生类(动态代理、反射等),会发生溢出
解决:JDK8以后永久代退出了历史舞台,可以通过调整元空间的大小或垃圾收集的频率来避免方法区溢出
程序计数器、虚拟机栈、本地方法栈三个区域线程私有,随线程的生命周期开始而开始,结束而结束。栈中的栈帧随着方法的进入和推出执行出栈和入栈操作,每一个栈帧分配的内存在编译期就已知,
堆和方法区中,在程序运行期间才能知道会创建哪些对象和创建多少对象,这部分内存的分配和回收是动态的。
java的GC:Java程序在运行期间,JVM会创建一个优先级较低的线程来作为垃圾回收的线程,该线程通过算法对对象的存活进行判定,并对已死的对象进行垃圾收集处理。
主流的JVM都不使用该算法进行垃圾回收,因为单纯使用该方法不能解决像对象之间相互循环引用等问题,需要对该算法进行额外的扩展。该方法十分简单,算法过程如下:
该算法为比较主流的判定对象存活算法。这个算法的基本思路为:
在Java中,GC Roots对象包括以下几种:(一般为栈、方法区中引用的对象,以及JVM内部的引用)
若每次都要对每一个对象进行是否回收判定,效率是比较低的。我们可以对java堆的内存空间再进行一个划分,划分为新生代、老年代、永久代(JDK8之前以永久代作为实现,而JDK8以后由元空间实现,且元空间占用的是本地内存)。而新生代又划分为Eden(伊甸园)和Survior(生存区,包含From和To)。
伊甸园:新创建的对象会进入新生代的伊甸园
Survior:新生的对象经过一次GC操作以后,依然存活的进入Survior中的From区
From区:当有对象第一次进入From区时,To区中的对象会进入到From区,随后进入To区
To:在To区进行年龄判定,继续进行GC操作,当GC操作判定对象年龄大于15(默认,经历一次GC年龄+1)时,该对象会进入老年代
空间担保机制:如果一次GC以后,新生代的依旧存在大量对象,Survior无法承载,则JVM会将Survior无法承载的部分送到老年代中,若老年代还是装不下,则会进行一次Full GC,若还是装不下,则发生内存溢出。
元空间:元空间不在java堆中,而是JVM申请的本地内存区域,这样就不会出现永久代存在的内存溢出问题,而且永久代的内存大小申请是难以确定的。
根据对象存活判定算法(如可达性分析或引用计算),对内存区当中需要被清理的对象进行标记,然后对其进行直接删除操作。
缺点:会时内存空间利用碎片化,空间利用效率不高
将内存分为两部分,一部分存放创建的对象,一部分为空。根据对象存活判定算法(如可达性分析或引用计算),对内存区当中需要被清理的对象进行标记,将标记的算法清除,然后将存活的对象剪切到内存为空的那部分区域。Survior区域(分为From和To)使用的就是这个算法。
缺点:不适用与老年代区域,因为老年代区域执行GC操作的对象较少,若每删除一个对象就要重新进行一次复制排序,效率较低。
不划分内存区域,根据对象存活判定算法(如可达性分析或引用计算),对内存区当中需要被清理的对象进行标记,然后对内存中的对象进行重新排序,将被标记的对象放在最后,然后清除。
缺点:效率较低。
经过jDK二十多年的更新迭代,出现了较多的垃圾收集器,从最开始的Serial收集器(复制算法)到CMS收集器(标记-清除算法),到JDK7和8的Garbage First收集器(标记-整理算法),实现了“全功能的垃圾收集器”,因其内存消耗问题,后续出现了第三方公司开发的Oracle不支持的shennandoah收集器,Oracle随后又发布了与其目标相似的ZGC收集器。
这里我们详细说明一下G1垃圾收集器的运行机制。在G1出现以前,像Serial收集器的范围是新生代、serial Old的范围是老年代。而G1收集器的目标范围包含了整个java堆。
G1将Java堆分为2048个大小相同的Region块,Region根据需要来扮演新生代或老年代角色,收集器对不同的角色进行不同的回收策略,其运作过程主要为以下四个步骤:
通过类的完全限定名称获取定义该类的⼆进制字节流。
将该字节流表示的静态存储结构转换为方法区的运行时存储结构。
在内存中生成⼀个代表该类的 Class 对象,作为方法区中该类各种数据的访问入口。
确保 Class ⽂件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机⾃身的安全。
准备阶段为类变量分配内存并设置初始值,使⽤的是⽅法区的内存。
实例变量不会在这阶段分配内存,它会在对象实例化时随着对象⼀起被分配在堆中。
实例化不是类加载的⼀个过程,类加载发⽣在所有实例化操作之前,并且类加载只进⾏⼀次,实例化可以进⾏多次。
将常量池的符号引⽤替换为直接引⽤的过程。
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了⽀持 Java 的动态绑定。
初始化阶段才真正开始执⾏类中定义的 Java 程序代码。初始化阶段是虚拟机执⾏类构造器
在准备阶段,类变量已经赋过⼀次系统要求的初始值,⽽在初始化阶段,根据程序员通过程 序制定的主观计划去初始化类变量和其它资源。
有下列五种情况必须对类进⾏初始化(加载、验证、准备都会随之发⽣):
所有引⽤类的⽅式都不会触发初始化, 称为被动引⽤:
虚拟机角度
启动类加载器(Bootstrap ClassLoader),使⽤ C++ 实现,是虚拟机⾃身的⼀部分;
所有其它类的加载器,使⽤ Java 实现,独⽴于虚拟机,继承⾃抽象类 java.lang.ClassLoader。
当然从开发人员的角度来看,还可以划分出:
应⽤程序是由三种类加载器互相配合从⽽实现类加载,除此之外还可以加⼊⾃⼰定义的类加载器。
下图展示了类加载器之间的层次关系,称为双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有⾃⼰的⽗类加载器。这⾥的⽗⼦关系⼀般通过组合 关系(Composition)来实现,⽽不是继承关系(Inheritance)。
其工作过程为:
简而言之,双亲委派模型就是,每个人要有个父亲,但是最开始的那个没有,而且父子关系不是通过继承来的,是通过认的,我们称为干爹。遇到事请了,儿子请教父亲、父亲请教他的父亲,一直请教到祖宗。如果父亲干不了,那他的儿子就只能自己想办法干。
参考:
【1】周志明——《深入理解Java虚拟机》