整理jvm概念和原理详解以及gc机制
注:源代码就是.java文件,JVM字节码就是.class文件
1. Java 堆(Java Heap):
(1)是Java虚拟机所管理的内存中最大的一块。
(2)在虚拟机启动的时候创建。堆是jvm所有线程共享的。
(3)唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。
2. JVM栈(java虚拟机栈):
(1)每个线程创建的同时会创建一个JVM栈帧,JVM栈中每个栈帧存放的为当前线程中局部基本类型的变量.
3. 本地方法栈(Native Method Stack):
(1)jvm中的本地方法是指方法的修饰符是带有native的,但是方法体不是用java代码写的另一类方法。
(2)作用同java虚拟机栈类似,区别是:虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
(3)是线程私有的,它的生命周期与线程相同,每个线程都有一个。
4. 方法区(Method Area):
(1)在虚拟机启动的时候创建。所有jvm线程共享。
(2)用于存放所有已被虚拟机加载的类信息、常量、静态变量、以及编译后的方法实现的二进制形式的机器指令集等数据。
5. 程序计数器(Program Counter Register):
也叫PC寄存器,是一块较小的内存空间,它可以看做是当前线程所执行的字节码的第几行号指示器。
在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
总结: 虚拟机栈、本地方法栈、程序计数器这三个模块是线程私有的,有多少线程就有多少个这三个模块,声明周期跟所属线程的声明周期一致。以程序计数器为例,因为多线程是通过线程轮流切换和分配执行时间来实现,
所以当线程切回到正确执行位置,每个线程都有独立的程序技术器,各个线程之间的计数器互不影响,独立存储。其余是跟JVM虚拟机的生命周期一致共享的。
————————————————
6.类加载器子系统(class loader subsystem):
(1)根据给定的类名(如java.lang.Object)来装载class文件的内容到Runtimedataarea中的methodarea(方法区域)。
(2)对(1)中的加载过程是:当一个classloader启动时,classloader的生存地点在jvm中的堆,然后它去主机硬盘上去装载A.class到jvm的methodarea(方法区)
7.执行引擎 :
(1)负责执行来自类加载器子系统(class loader subsystem)中被加载类中在方法区包含的指令集,通俗讲就是类加载器子系统把代码逻辑
(什么时候该if,相加,相减)都以指令的形式加载到了方法区,执行引擎就负责执行这些指令就行了。
8.解释器:
一条一条地读取,解释并且执行字节码指令。因为它一条一条地解释和执行指令,所以它可以很快地解释字节码,但是执行起来会比较慢。这是解释执行的语言的一个缺点。字节码这种“语言”基本来说是解释执行的。
9.编译器:
(1. 即时编译器被引入用来弥补解释器的缺点。执行引擎首先按照解释执行的方式来执行,然后在合适的时候,即时编译器把整段字节码编译成本地代码。
(2. 然后,执行引擎就没有必要再去解释执行方法了,它可以直接通过本地代码去执行它。执行本地代码比一条一条进行解释执行的速度快很多。编译后的代码可以执行的很快,因为本地代码是保存在缓存里的。
描述一下JVM加载class文件的原理机制?
JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。
1.装载:查找和导入class文件;new对象隐式装载,反射类显示装载,看log日志就知道什么是隐式/显式
2.连接:
(1)检查:检查载入的class文件数据的正确性;
(2)准备:为类的静态变量分配存储空间;
(3)解析:将符号引用转换成直接引用(这一步是可选的)
3.初始化:初始化静态变量,静态代码块。
这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。
9.1. jdk,jre,JVM的关系:
JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,
在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。
10. 上图,堆内存分为三部分:
(1.新生区:是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。
(2.养老区:用于保存从新生区筛选出来的 JAVA 对象,一般池对象都在这个区域活跃。
(3.永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,
也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。
11. 出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。
原因有二:
(a.Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。
(b.代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。
12.双亲委派机制:
JVM在加载类时默认采用的是双亲委派机制。通俗的讲:
就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,(bootStrap、extclassLoader、appclassloader三个是父子类加载器)
如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
13. 什么时候会发生Full GC?
(1)调用System.gc()方法的
(2)老年代空间不足。【老年代空间只有在新生代对象转入及创建大对象、大数组时才会出现不足的现象】
(3)永生区空间不足。
(4)堆中分配很大的对象。【例如很长的数组,此种对象会直接进入老年代】
(5)CMS GC时出现promotion failed和concurrent mode failure
更详细的解释说明连接;https://blog.csdn.net/qq_38384440/article/details/81710887
14. gc回收的内容:
gc的主要作用是回收堆中的对象。通过分析一个对象的引用是否存在,如果不存在,就可以被回收了。
15.gc的具体过程:
这个主要看是用的哪一种回收算法以及用的什么垃圾回收集了。回收算法主要有:
(1)标记-清除算法:
该算法先标记,后清除,将所有需要回收的对象进行标记,然后清除;这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象会触发GC回收,造成内存浪费以及时间的消耗。
(2)标记-整理算法(老年代):
针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。
(3)复制算法(新生代):
该算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。
(4)分代收集算法:
分代收集算法就是目前虚拟机使用的回收算法。
16. 常用的垃圾回收器:
(1)Serial收集器【串行收集器】
(2)ParNew收集器【串行收集器的多线程版本】
(3)Parallel Scavenge收集器【PS收集器】
(4)CMS【老年代收集器】
(5)G1收集器
关于垃圾回收器的使用,这里也有一个组合建议共大家参考: