JVM知识点总结

148 阅读6分钟

Java内存泄露

Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露。

对象都是有生命周期的,有的长,有的短,如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露。我们举一个简单的例子:

public class Simple {
    Object object;
    public void method1(){
        object = new Object();
        //...其他代码
    }
}

这里的object实例,其实我们期望它只作用于method1()方法中,且其他地方不会再用到它,但是,当method1()方法执行完成后,object对象所分配的内存不会马上被认为是可以被释放的对象,只有在Simple类创建的对象被释放后才会被释放,严格的说,这就是一种内存泄露。解决方法就是将object作为method1()方法中的局部变量。当然,如果一定要这么写,可以改为这样:

public class Simple {
    Object object;
    public void method1(){
        object = new Object();
        //...其他代码
        object = null;
    }
}

JVM的内存模型

主要有堆,方法区,虚拟机栈,本地方法栈,程序计数器,其中堆和方法区是线程共享的,栈和计数器是线程私有的。堆存放对象实例。方法区存放已被虚拟机加载的类信息、常量、静态变量。虚拟机栈的作用是每一个线程创建一个单独的运行时堆栈,对于每一个方法的调用,一个栈内存栈帧被创建。本地方法栈的作用是服务于虚拟机使用到的native方法。程序计数器用于记录下一个指令。

JVM8为什么要增加元空间,带来什么好处

  1. 字符串存在永久代中,容易出现性能问题和内存溢出。
  2. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢 出,太大则容易导致老年代溢出。
  3. 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
  4. 元空间使用直接内存,解决上述问题

堆内存的新生代和老年代以及垃圾回收算法

image-20211009161702872.png

垃圾回收算法

垃圾:没有任何引用指向的东西。

找到垃圾的算法:

  1. reference count 引用计数,无法解决循环引用的问题
  2. root searching 根可达算法,也就是从根节点开始搜索。GC roots(哪些可以为根):线程栈变量,静态变量,常量池,JNI指针

回收垃圾的算法

  1. mark-sweep 标记清除,在存活对象比较多的情况下效率较高。他需要从根扫两遍,第一遍是找到垃圾,第二遍是清除垃圾,所以存活对象多的情况下,第二遍清除的垃圾也就少,效率也就随之变高。
  2. copying 拷贝,适用于存活对象较少的情况。将内存一分为2,一半空,一半存放数据,回收时,将存放数据那一半中存活的对象依次拷贝到另一半上。适用于新生代。
  3. mark-compact,标记压缩。找到存活的对象,将他们挪动到要清除的位置或空闲位置,让存活的对象的内存空间最终连续。适用于老年代。

结合老年代的新生代

新生代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Survivor Space(命名为A和B)。当对象在堆创建时,将进入年轻代的Eden Space。垃圾回收器进行垃圾回收时,扫描Eden Space和A Suvivor Space,如果对象仍然存活,则复制到B Suvivor Space,如果B Suvivor Space已经满,则复制到Old Gen。同时,在扫描Suvivor Space时,如果对象已经经过了几次的扫描仍然存活,JVM认为其为一个持久化对象,则将其移到Old Gen。扫描完毕后,JVM将Eden Space和A Suvivor Space清空,然后交换A和B的角色。年老代主要存放JVM认为生命周期比较长的对象,垃圾回收也相对没有那么频繁,年老代主要采用压缩的方式来避免内存碎片。

jvm有哪些垃圾回收器,实际中如何选择?

  • 新生代收集器(全部的都是复制算法):Serial、ParNew、Parallel Scavenge
  • 老年代收集器:CMS(标记-清理)、Serial Old(标记-整理)、Parallel Old(标记整理)
  • 整堆收集器: G1(一个Region中是标记-清除算法,2个Region之间是复制算法)
  1. Serial:单线程、简单高效
  2. ParNew:Serial收集器的多线程版本
  3. Parallel Scavenge:与吞吐量关系密切,故也称为吞吐量优先收集器。Parallel Scavenge收集器使用两个参数控制吞吐量: XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间;XX:GCRatio 直接设置吞吐量的大小。
  4. Serial Old:Serial收集器的老年代版本。单线程收集器。
  5. Parallel Old:是Parallel Scavenge收集器的老年代版本。多线程收集器。
  6. CMS:一种以获取最短回收停顿时间为目标的收集器。并发收集、低停顿。
    CMS收集器的运行过程分为下列4步:
    • 初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
    • 并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。
    • 重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记 录。仍然存在Stop The World问题。
    • 并发清除:对标记的对象进行清除回收。
  7. G1:既可并发,也可并行,也不会产生碎片。

class初始化过程

  • 父类初始化
  • 静态代码块、静态变量的初始化(前提是该静态变量被实例化了),两者按顺序执行,并且只会初始化一次
  • 构造代码块、普通变量的初始化(前提是该普通变量被实例化了),两者按顺序执行
  • 构造函数

双亲委派机制

在某个类加载器加载class文件时,它首先委托父加载器去加载这个类,依次传递到顶层类加载器 (Bootstrap)。如果顶层加载不了(它的搜索范围中找不到此类),子加载器才会尝试加载这个类。

双亲委派的好处:每一个类都只会被加载一次,避免了重复加载,每一个类都会被尽可能的加载(从引导类加载器往下,每个加载器都可能会根据优先次序尝试加载它)