文章目录
1.JVM 内存结构
JVM 内存结构由4部分组成:方法区、堆、栈、程序计数器。
- 方法区:
方法区是 JVM 中定义的一块内存区域,用来存放类的信息。
我们写的 java 源代码,通过 javac 编译成二进制字节码,就能被 JVM 识别。
JVM 就会调用类加载子系统,将类的相关信息加载到方法区,就可以去执行类里面的代码。
遇到没有见过的类,会继续触发类加载过程。
- 堆内存
如果创建对象,对象就保存在堆中。
不再使用的对象,就会被垃圾回收,释放掉内存。
参数设置:
-Xms //初始堆大小
-Xmx //最大堆大小
- 栈:
调用方法时,方法内的局部变量,还有方法的参数,都是引用,保存在栈中。
可以通过参数-Xss
来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。
- 程序计数器:
程序计数器记录的是执行下一条指令的地址。因为多线程交替执行,如果发生多线程上下文切换,下次就能知道从第几行代码继续执行。
1.1 会发生内存溢出的区域
JVM内存结构里,除了程序计数器不会发生内存溢出,其他几个都会。
因为程序计数器的生命周期是由线程的创建而创建,随着线程的结束而结束。
-
方法区内存溢出:加载的类越来越多,许多框架在运行期间会动态产生很多新的类
-
堆内存溢出:创建的对象太多,一直在使用,没有被垃圾回收
-
栈内存溢出:方法的递归调用次数太多
1.2 方法区、永久代、元空间
永久代和元空间只是叫法不同。
-
永久代是 Hotspot 虚拟机 JDK 1.8 之前 对方法区的实现
-
元空间是 Hotspot 虚拟机 JDK 1.8 以后 对方法区的实现
-
元空间使用的是本机内存。参数设置:
-XX:MetaspaceSize=N //设置Metaspace的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置Metaspace的最大大小
2. 垃圾回收
先介绍什么是垃圾回收?为什么要垃圾回收?
垃圾回收针对的是堆内存里的对象。
我们不能无限制地创建对象,而且不是所有对象都需要一直存在。如果没有垃圾回收,那么内存总有一天会用完的。
垃圾回收的目的就是找到那些无用的对象,释放掉它们的内存。
怎么判断哪些对象是垃圾?
如果一个对象没有任何引用指向它,那么这个对象就是垃圾。
有2种方式来确定对象是不是垃圾:引用计数法和可达性分析算法。
2.1 引用计数法
一个对象被引用了一次,那么对象的引用次数就 +1,当引用次数为 0 时,就说明这个对象是垃圾。
但是引用计数法存在一个问题:当对象间出现了循环引用的话,会导致内存泄露(内存使用完无法释放),所以不使用这种方法。
2.2 可达性分析算法
现在的虚拟机都采用可达性分析算法来确定对象是不是垃圾。
可达性分析算法:沿着 GC Root 引用链,看看有没有关联的对象,关联的对象就不会被垃圾回收。
哪些对象可以作为 GC Root?
-
- 栈的引用可以作为 GC Root,因为它指向堆里的对象 ( Student s = new Student(),这个 s 就可以作为 GC Root)
-
- 方法区中的静态变量可以作为 GC Root,因为它可以是引用,指向堆里的对象。
对象有哪些引用类型?
Java 具有四种强度不同的引用类型。
- 强引用
被强引用关联的对象不会被回收。这是使用最普遍的引用
使用 new 一个新对象的方式来创建强引用。
Object obj = new Object();
- 软引用
被软引用关联的对象只有在内存不够的情况下才会被回收。
使用 SoftReference 类来创建软引用。
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联
- 弱引用
被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。
使用 WeakReference 类来实现弱引用。
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
- 虚引用
又称为幽灵引用或者幻影引用。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象。
为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知。
使用 PhantomReference 来实现虚引用。
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj = null;
2.3 JVM 垃圾回收算法
垃圾回收算法有 3 种:标记清除法、标记整理法、标记复制法。
标记就是找到那些不能被垃圾回收的对象,标记起来。将来垃圾回收发生了,没有标记的对象就是垃圾,将它们回收。
-
标记清除法:分为2个步骤,先标记,再清除。因为是连续的内存空间,被清除的部分有些太小了,不能存下更大的对象。
它的缺点就是:会产生内存碎片,空间不能很好地利用起来,所以现在没有 JVM 使用这种算法了。
-
标记整理法:标记整理法对标记清除法做了改进。多了整理的动作,将对象向内存的一端移动,这样就避免了内存碎片的产生。但是整理的动作会降低效率。
-
标记复制法:将内存分为 2 个大小相等的区域,1个区域存对象,一个区域空着。垃圾回收时将存活的对象放到空的区域,原来的区域里的垃圾清除。缺点是占用了成倍的空间,空间利用率比较低。
标记复制法适合新生代的垃圾回收,因为新生代的存活对象比较少,复制就比较少。
标记整理法适合老年代的垃圾回收,因为老年代的存活对象比较多,整理就比较少。
2.4 分代垃圾回收
现在的 JVM 都采用分代垃圾回收的思想:(分代的目的就是优化 GC 性能):
根据不同对象的生命周期,将回收区域分为新生代和老年代。
用完就丢的对象放在新生代。
长时间存活的对象放在老年代。
新生代又分为三大区域,伊甸园 , 幸存区 From 和幸存区 To。当幸存区的对象熬过最多 15 次的垃圾回收,晋升到老年代。
根据 GC 的规模分为 Minor GC 和 Full GC。
新生代空间不足的时候触发 Minor GC。
老年代空间不足的时候会先尝试触发 Minor GC,如果之后空间仍不足,那么触发 Full GC。
3. 类加载过程
类加载过程分为3个阶段:加载、链接、初始化。
- 加载:将类的信息通过类加载子系统,加载到方法区
- 链接:
- 检查类的信息是不是合法
- 为 static 修饰的静态变量分配内存
- 将常量池的符号引用解析为直接引用
- 初始化:执行静态代码块、给 static 修饰的静态变量赋值
4. 双亲委派机制
双亲委派机制是类加载器的一种工作方式。
就是优先委派上级类加载器对类进行加载。
如果上级类加载器
找得到这个类,就由上级加载。
找不到这个类,就由下级加载。