【JVM】垃圾回收 ——自问自答2

Q: System.gc() 的理解

System.gc()底层调用的是 Runtime.getRuntime.gc(),会现实出发FullGC。

但是,它的调用附带一个免责声明,无法保证对垃圾收集器的调用。

Q: 内存溢出和内存泄漏?

内存溢出: 简而言之,内存不够用了

可能因为堆内存在设置大小的时候 -Xms ,-Xmx 设置的比较小

前面也提到,虚拟机栈可以动态扩容时,当物理内存空间不足以给栈扩容,也会导致OutofMemory:stack

在jdk不同版本之中,方法区所产生的OOM是不同的。

jdk7及以前,是永久代 permgen space,而jdk8之后则是 Metaspace

一般情况下,在系统抛出OOM之前,GC会进被触发,尽可能清理出空间给新对象。

比如软引用就是在内存不足的情况下,会被触发回收。

内存泄漏(memory Leak)

当一个对象不会再被程序用到了,但是GC有不能回收他们,此时称之为内存泄漏。(一个不再被使用的对象,仍然有GC roots引用)

内存泄漏的举例:

1、单例模式。

首先明白一个问题,一个JVM对应的是一个进程(有一个与之对应的单例对象 Runtime),

平时说的微服务也好,分布式也好,不管怎样,请搞清楚。一个java进程,对应一个JVM,或者可以理解为,一个运行起来的服务,它就是一个进程。

如果在这个服务(进程)中,有一个单例对象,那么此对象的生命周期是随着JVM进程的消亡而消亡的。

如果此时单例对象内强引用了一个其他对象,而没有手动释放,那么这个被引用的对象的生命周期会和引用它的单例对象一样,一直随着JVM进程结束,才能结束。

如果这个被引用的对象后续不再被使用,但是它无法被GC销毁,这时候,就是个典型的内存泄漏场景。

我们可以用软引用来解决这种问题。

2、没用关闭某些资源

java的面向对象思想,把很多资源使用场景都有一个建立链接和关闭链接的过程,这个其实是一种网络分层模型中,提供可靠服务的前提。

比如数据库链接,套接字链接(socket),IO,他们都是要进行手动close的。前两个其实都是基于TCP的建立连接和关闭链接的过程。

Q: 谈一谈Stop the World

STW GC线程在工作的过程中,与用户线程是并发执行的,二者抢占CPU资源,GC执行,用户线程被暂停。

可达性分析算法中枚举根节点会导致Java执行线程停顿。

因为: 分析工作要保证一致性结果,一般是在当前进程快照中分析

如果分析过程中,一直有用户线程在执行,就会导致分析结果不准确。

(开发中尽量少使用System.gc 会产生STW)

Q: 垃圾回收的并行,并发。

垃圾回收事件的并行与串行是对立的,它描述的是垃圾收集线程的工作关系。

串行,指在进行垃圾收集时,只有一个GC线程在工作。

并行:指在进行垃圾收集时,有多个GC线程在同时工作。

注意上述两个场景中,用户线程都是暂停的,并行与串行机制只是在描述垃圾收集线程。

但是请注意一点:并行,虽说是要建立在多处理器条件下,才可能发生的,但是在操作系统相关的知识体系中,"并行"的概念并不是绝对的。

因为并行的垃圾回收行为,在单处理器环境下,是通过并发执行表现的。但是请注意发生CPU争抢的,只有GC线程而没有用户线程。

这个道理其实就像理解操作系统的共享特性。共享分为独占式共享,和同时共享。

独占式共享,就是串行的,一个作业完成对资源的使用后,再运行下一个作业去访问资源。

但是同时访问共享,并不真是"同时访问",本质上来说,也是通过并发的特性来实现"同时"。

垃圾收集的并发行为:

指的是垃圾收集线程,可以和用户线程 "同时"执行(这里的同时,和上面的解释是一样的,还是取决于CPU和工作线程的个数)

因此,垃圾收集上下文环境所说的并发和并行,和操作系统层面说的并发,并行是有区别的。

Q: 安全区和安全点

程序执行时并不是在任何地方都能停下来开始GC,只是在特定位置才能停顿下来,开始GC,这些位置称为 安全点

安全点的选择一般是以"是否具有让程序长时间执行的特征",比如选择一些执行时间较长的指令作为安全点,比如方法调用,循环跳转和异常跳转。

如何在GC发生时,检查所有线程都跑到了最近的安全点停顿下来呢?

1、抢占式中断:弃用

2、主动式中断:设置标志位,各个线程运行到Safepoint,主动轮训这个标志,若中断标志为真,将自己进行中断挂起。

这里补充一个操作系统的知识

进程在执行的过程时,CPU会有内核态和用户态,当发生系统中断,CPU会从用户态转换为内核态,使操作系统夺回CPU控制权(也是唯一途径)

内核态->用户态:执行一条特权指令----修改PSW的标志位为用户态。操作系统让出CPU使用权。

用户态->内核态:由 中断 引发,硬件自动完成变态过程。

中断分为 内中断和外中断,一般程序执行遇到异常,或请求系统调用,执行访管指令都属于内中断。

当进入中断时,用户进程根据不同场景会进入就绪,阻塞,就绪/阻塞挂起,退出等状态。

操作系统关于线程实现方式有三类:内核级线程、用户级线程、组合方式

用户级线程:(图片来自王道,侵删)

早期,操作系统只支持进程,没用线程的概念,内核中只有进程的概念,进程作为资源分配的单位,

用户通过线程库来对一个进程进行拆分执行。最简单的线程库,可以理解为:

cpp 复制代码
int main(){
    int flag =0;

    while(true){
    
        if(flag==0){
            do A;
        }
        if(flag==1){
            do B;
        }
        if(flag==3){
            do C;
        }
   }

}

用户线程是建立在用户空间的线程库上,系统内核不感知线程存在的实现,用户线程的创建、同步、销毁和调度完全在用户状态中完成。

但是这种设计的时间片划分仍然是以进程为单位的,进程A有一个线程,进程B有100个线程,假设两者的时间片长度相同,这种分配结果是不公平的。

而且,某一线程被阻塞,整个进程都会被阻塞,内核分配CPU资源也是按进程分配,也就是说进程B中的所有线程都是并发执行,每次只能执行1个线程。

内核级线程

操作系统内核支持的线程,这种线程有内核来完成线程切换。每个线程都有自己的TCB

换句话说,操作系统内核已经支持线程的概念了,内核已经将线程作为最小的资源分配单位。

用户线程的实现是通过线程库来创建管理的,线程库分为有内核支持和无内核支持,区别在于在调用库内函数时,是否需要进行内核的系统调用。

java线程库就是常用的线程库之一。它以来与宿主系统的线程库,windows上是 windows API,Unix上是POSIX pthreads。

这些模型是建立在支持内核级线程的系统中的:

多线程模型

用户线程和内核线程链接方式不同,分为一对一,多对一,多对多。

一对一 :一个用户线程对应一个内核线程,用户线程被阻塞,并不会影响其他线程的运行。

并发能力较强,一个线程阻塞,并不会影响到其他线程,但是一一对应开销也很大。(见上图)

多对一 :一个内核线程对应多个用户线程,这些线程一般属于一个进程,线程的调度和管理都是在用户空间完成的(执行效率比较高)。

缺点很突出:任意时刻,只能有一个线程与内核线程进行映射,一个线程阻塞,整个进程会发生阻塞,多个线程不能同时在多个处理器上运行。

java每个线程都对应了一个本地线程,其实java的线程是在JVM虚拟机中通过一些C++写的方法调用,来调用本地线程库的API,对线java程状态进行管理,映射。

所有这个对应的本地线程,实际上是本地线程库中的用户级的线程,而非内核线程,至于本地线程库到底使用了那种多线程模型,要具体系统具体分析,这也是JVM实现跨平台的一个手段。

这里补充这些知识,是想说,用户线程在安全点的STW,其受到中断而挂起,其实是通过

JVM------本地线程库------内核级线程状态切换 这一套执行流程的。这个安全点造成的中断,并非操作系统里说的中断,OS里的中断粒度是针对进程来说的。

安全区:

如果某些线程在准备进入安全点之前,是阻塞态,该线程此时是无法响应JVM的中断请求的,运行到安全点,再中断挂起,

安全区是一段代码片段中,对象的引用关系没有发生变化,再这个区域中任何位置开始GC都是安全的。

从一个点 被扩大成了一个片段。

执行过程:

当线程运行到safe Region代码段时,首先标识已经进入了safe Region,若这段时间内发生GC,JVM会忽略标识为 safe Region状态的线程。

当线程即将离开safe Region时,会检查JVM是否已经完成GC,若完成了,则继续运行,否则线程必须等待到收到可以安全离开safe Region的信号为止。

Q: 强软弱虚来一套

强引用:普通创建对象的语法,都是强引用。

软引用:堆内内存不足时,软引用会被回收

弱引用:发生垃圾回收行为时,一定会被回收

虚引用:一个对象是否有虚引用存在,跟其生存时间没叼毛影响,无法通过一个虚引用获取一个对象实例。

设置虚引用的目的在于垃圾回收该对象时,得到一个系统通知。它其实可以理解为一个对象回收的跟踪。对象回收后,将虚引用加入引用队列,以通知程序该对象的回收情况

java 复制代码
//-xms9m -xmx9M  -XX:+PrintGCDetails

class ReferenceDemo{
        

    //强引用,ReferenceDemo对象实例化后,objRf_inheap位于实例对象所在的堆空间中
    Object objRf_inheap =new Object();
    
 
    public void foo(){
    //强引用,引用存在于局部变量表中的第二个位置,slot标号为1 
    Object objRf_inLV =new Object();
    
    //软引用,当内存空间不足的时候,会在GC中对其进行回收
    Object obj =new Object();
    SoftReference obj_softRf =new SoftReference<Object>(obj);
    obj=null;
    //可以获取代到软引用包装的对象
    obj_softRf.get();
    
    //创建一个大对象,当前堆装不下了,目的是要触发GC,堆内内存不足时,软引用会被回收
    byte[] buffers =new byte[7*1024*1024];

    ....
}

@Override
protected void finalize() throws Throwable {
    super.finalize();
    System.out.println("我被调用了呀!!!!");
}
}
复制代码
相关推荐
2202_754421548 分钟前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
蓝染-惣右介11 分钟前
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
java·数据库·tomcat·mybatis
小林想被监督学习12 分钟前
idea怎么打开两个窗口,运行两个项目
java·ide·intellij-idea
HoneyMoose14 分钟前
IDEA 2024.3 版本更新主要功能介绍
java·ide·intellij-idea
我只会发热15 分钟前
Java SE 与 Java EE:基础与进阶的探索之旅
java·开发语言·java-ee
是老余16 分钟前
本地可运行,jar包运行错误【解决实例】:通过IDEA的maven package打包多模块项目
java·maven·intellij-idea·jar
crazy_wsp17 分钟前
IDEA怎么定位java类所用maven依赖版本及引用位置
java·maven·intellij-idea
.Ayang19 分钟前
tomcat 后台部署 war 包 getshell
java·计算机网络·安全·web安全·网络安全·tomcat·网络攻击模型
一直学习永不止步25 分钟前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表
hummhumm39 分钟前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j