JVM初探:JVM内存架构,内存分区,GC垃圾回收算法,双亲委派等

JVM内存架构

图示:
image.png
其中方法区、堆为共有,程序计数器、本地方法栈、虚拟机栈为线程私有。

  • 方法区
    主要保存类信息、常量池、静态变量和方法,类与对象的对应关系等

  • 存放类对象,在堆里,类对象可根据存活时间长短分为新生代、老年代、永久代等。
  • 程序计数器
    用来存储指向下一条指令的地址,也即将将要执行的指令代码。由执行引擎读取下一条指令。
  • 本地方法栈
    调用本地方法,Native方法,不限制语言
  • 虚拟机栈
    调用Java方法,栈中数据以栈帧出现,其保存函数的返回地址、参数、临时变量、函数调用上下文等

GC垃圾回收机制

分区图例:
image.png

新生代

  • 伊甸园区
    创建的新对象将会存在于该区,当进行gc时,该区中存活的对象将会转移至幸存区。
  • 幸存区
    该区分为0区和1区,谁空谁是0区,即两个区必有一个是空的,在该区gc时使用的是复制算法。在经历过一次gc后,存活下来的对象年龄进行+1,当年龄为15时,将该对象转移至老年代。
  • 复制算法
    当新生代进行GC(轻gc)时,会将伊甸园区存活的对象和幸存1区存活的对象复制进入幸存0区,清空1区,此时0区和1区互换,0区变为1区,1区变为0区。
    优点:复制算法主要解决了当时年代的标记清除算法的效率问题,标记清除算法需要扫描两次空间,并且内存不连续,而复制算法不需要标记,空间连续,
    缺点:复制算法会使用两块空间,一块为空,导致了空间的浪费。如果需要复制的对象较多,那么该算法效率并不高,但是在新生代中,往往大部分对象难以存活,所以该算法恰恰适用于新生代。

老年代:

新生代中的对象年龄为15时进入老年代,该区对象存活时间较长,当该区没有连续的空间存放新进入的对象时,则自动进行gc(重gc),该gc算法采用标记清除算法

  • 标记清除算法
    顾名思义,先标记再清除。标记不需要回收的对象,堆没有标记的对象进行回收。
    优点:简单常用
    缺点:效率不算高(两次O(n)),在进行GC的时候,需要停止整个应用程序,导致用户体验差,这种方式清理出来的==空闲内存是不连续的,产生内存碎片==。
    对于内存不连续问题,可采用进化版 标记清除压缩算法,即对存活下来的对象进行整理到连续空间。

可达性分析

  • 可达性分析算法
    也可以称为根搜索算法,该算法可以有效地解决在引用计数算法中循环引用的问题,
    基本思想
    可达性分析算法是以根对象集合(GCRoots,所谓 "GC Roots”根集合就是一组必须活跃的引用,由于 Root 采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个 Root。)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
    使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)。

如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。

在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。

  • GC Roots 可以是哪些?
    虚拟机栈中引用的对象,比如:各个线程被调用的方法中使用到的参数、局部变量等。
    本地方法栈内 JNI(通常说的本地方法)引用的对象
    方法区中类静态属性引用的对象,比如:Java类的引用类型静态变量
    方法区中常量引用的对象,比如:字符串常量池(string Table)里的引用
    所有被同步锁 synchronized 持有的对象
    Java虚拟机内部的引用。
    基本数据类型对应的 Class 对象,一些常驻的异常对象(如:NullPointerException、OutOfMemoryError),系统类加载器。
    反映 java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。
  • 总结
    一句话就是,堆空间外的一些结构,比如虚拟机栈、本地方法栈、方法区、字符串常量池等地方对堆空间进行引用的,都可以作为 GC Roots 进行可达性分析。

除了这些固定的 GC Roots 集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整 GC Roots 集合。比如:分代收集和局部回收(Partial GC)。

注意
如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证。
这点也是导致 GC进行时必须“stop The World”的一个重要原因。
即使是号称(几乎)不会发生停顿的 CMS 收集器中,枚举根节点时也是必须要停顿的。

双亲委派机制

图示:
image.png

工作流程
如果一个类加载器收到了类加载的请求,它并不会自己加载,而是先把请求委托给父类的加载器执行,如果父类加载器还有父类,则进一步向上委托,依次递归,请求到达最顶层的引导类加载器。如果顶层类的加载器加载成功,则成功返回。如果失败,则向下委派子加载器会尝试加载,直到加载成功。
优缺点
避免类的重复加载
当自己程序中定义了一个和Java.lang包同名的类,此时,由于使用的是双亲委派机制,会由启动类加载器去加载JAVA_HOME/lib中的类,而不是加载用户自定义的类。此时,程序可以正常编译,但是自己定义的类无法被加载运行。
保护程序安全,防止核心API被随意篡改。