JVM

本文探讨了JVM的内存区域,包括类加载器、运行时数据区和执行引擎。详细阐述了JVM内存的划分,如栈、堆、方法区及其子区域。介绍了垃圾回收的概念,强调了GC Roots在确定垃圾对象中的作用,并讨论了不同代的垃圾回收算法。此外,文章还提及了G1垃圾回收器的特点,包括其无碎片和可预测停顿时间的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

!.从进阶到脑死亡的问题
前言
聊聊常见的面试题

1、请你谈谈你对JVM的理解? java8 虚拟机有什么更新?

2、什么是OOM,请你说说OOM产生的原因?如何分析?

3、JVM 的常用调优参数有哪些?

4、内存快照抓取,如何分析,命令是什么?

5、堆里面分区:Eden、Survial(from to)、老年区

6、GC垃圾收集算法有那个几个?谈谈利弊?

BAT 难度的面试题

1、JVM 垃圾回收的时候如何确定垃圾,GC Roots?

2、-X、 -XX 参数你用过哪些?

3、你常用的项目,发布后配置过JVM 调优参数吗?

4、引用、强引用、弱引用、虚引用都是什么,请你谈谈?

5、GC垃圾回收器和GC算法的关系?分别有哪些?

6、谈谈默认的垃圾回收器?

7、G1垃圾回收器的特点?

8、OOM 你看过几种?

1.1什么是jvm

JVM是一种用于计算设备的规范,它是一个虚构出来的计算机
使用JVM就是为了支持与操作系统无关,实现跨平台

1.2JVM内存区域划分

JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。

在这里插入图片描述1.2.1类加载器ClassLoader

在这里插入图片描述类的加载、链接和初始化(了解)

加载:查找并加载类的二进制数据

连接:

  • 验证:保证被加载的类的正确性;
  • 准备:给类静态变量分配内存空间,赋值一个默认的初始值;
  • 解析:把类中的符号引用转换为直接引用
    在把java编译为class文件的时候,虚拟机并不知道所引用的地址;助记符:符号引用!
    转为真正的直接引用,找到对应的直接地址!

初始化:给类的静态变量赋值正确的值;

public class Test{
    public static int a = 1;
}
// 1、加载   编译文件为 .class 文件,通过类加载,加载到JVM

// 2、连接   
	  验证(1)  保证Class类文件没有问题
      准备(2)  给int类型分配内存空间,a = 0;
      解析(3)  符号引用转换为直接引用

// 3、初始化
      经过这个阶段的解析,把1 赋值给 变量 a;

// JVM 参数:
//     -XX:+TraceClassLoading // 用于追踪类的加载信息并打印出来
//     分析项目启动为什么这么慢,快速定位自己的类有没有被加载!
// rt.jar jdk 出厂自带的,最高级别的类加载器要加载的!
public class Demo02 {
    public static void main(String[] args) {
        System.out.println(MyChild1.str2);
        // 运行的结果
        /**
         * MyParent1 static
         * MyChild1 static
         * hello,str2
         */
    }
}

class MyParent1{
    public static String str = "hello,world";
    static {
        System.out.println("MyParent1 static");
    }
}

class MyChild1 extends MyParent1{
    public static String str2 = "hello,str2";           //如果final修饰  只会打印 "hello,str2" 常量在编译阶段的时候 常量池
    static {
        System.out.println("MyChild1 static");
    }
}

ClassLoader 分类

1、java虚拟机自带的加载器

  • BootStrap 根加载器 (加载系统的包,JDK 核心库中的类 rt.jar)
  • Ext 扩展类加载器 (加载一些扩展jar包中的类)
  • Sys/App 系统(应用类)加载器 (我们自己编写的类)

2、用户自己定义的加载器

  • ClassLoader,只需要继承这个抽象类即可,自定义自己的类加载器

双亲委派机制

双亲委派机制 可以保护java的核心类不会被自己定义的类所替代

一层一层的让父类去加载,如果顶层的加载器不能加载,然后再向下类推

// Demo01
// AppClassLoader       03
// ExtClassLoader       02
// BootStrap (最顶层)   01  java.lang.String  rt.jar

1.2.2.运行时数据区
Stack 栈是什么

栈就是管理程序运行的

存储一些基本类型的值,对象的引用,方法等…

在这里插入图片描述栈的优势:存取速度比堆快!仅次于寄存器,栈的数据是不可以共享的;

栈原理
java栈的组成元素–栈帧
在这里插入图片描述栈(存什么)+ 堆 + 方法区的交互图:
在这里插入图片描述我们的这个栈主要是 HotSpot (指针)

假设你的公司用的 JVM 不是HotSpot

问题:请你谈谈你认识几种 JVM? (3种)

  • SUN 公司 HotSpot (掌握即可)
  • BEA 公司 JRockit
  • IBM 公司 J9VM

堆(heap)

Java7之前:

Heap 堆,一个JVM实例中只存在一个堆,堆的内存大小是可以调节的。

可以存的内容:类、方法、常量、保存了类型引用的真实信息;

分为三个部分:

  • 新生区:Young (Eden-s0-s1)
  • 养老区:Old Tenure
  • 永久区:Perm

堆内存在逻辑上分为三个部分:新生、养老、永久(JDK1.8以后,叫元空间)

物理上只有 新生、养老;元空间在本地内存中,不在JVM中!

GC 垃圾回收主要是在 新生区和养老区,又分为 普通的GC 和 Full GC,如果堆满了,就会爆出 OutOfMemory;

新生区

新生区 就是一个类诞生、成长、消亡的地方!

新生区细分: Eden、s(from to),所有的类Eden被 new 出来的,慢慢的当 Eden 满了,程序还需要创建对象的时候,就会触发一次轻量级GC;清理完一次垃圾之后,会将活下来的对象,会放入幸存者区(),… 清理了 20次之后,出现了一些极其顽强的对象,有些对象突破了15次的垃圾回收!这时候就会将这个对象送入养老区!运行了几个月之后,养老区满了,就会触发一次 Full GC;假设项目1年后,整个空间彻彻底底的满了,突然有一天系统 OOM,排除OOM问题,或者重启;

Sun HotSpot 虚拟机中,内存管理(分代管理机制:不同的区域使用不同的算法!)

Eden from to

99% 的对象在 Eden 都是临时对象;

养老区

15次都幸存下来的对象进入养老区,养老区满了之后,触发 Full GC

默认是15次,可以修改!

永久区(Perm)

放一些 JDK 自身携带的 Class、Interface的元数据;

几乎不会被垃圾回收的;

OutOfMemoryError:PermGen 在项目启动的时候永久代不够用了?加载大量的第三方包!

JDK1.6之前: 有永久代、常量池在方法区;

JDK1.7:有永久代、但是开始尝试去永久代,常量池在堆中;

JDK1.8 之后:永久代没有了,取而代之的是元空间;常量池在元空间中;

闲聊:方法区和堆一样,是共享的区域,是JVM 规范中的一个逻辑的部分,但是记住它的别名 非堆

元空间:它是本地内存!

/**
 * 默认情况:
 * maxMemory : 1808.0MB (虚拟机试图使用的最大的内存量  一般是物理内存的 1/4)
 * totalMemory : 123.0MB (虚拟机试图默认的内存总量 一般是物理内存的 1/64)
 */
// 我们可以自定堆内存的总量
// -XX:+PrintGCDetails; // 输出详细的垃圾回收信息
// -Xmx: 最大分配内存; 1/4
// -Xms: 初始分配的内存大小; 1/64

// -Xmx1024m -Xms1024m -XX:+PrintGCDetails
public class Demo01 {
    public static void main(String[] args) {
        // 获取堆内存的初始大小和最大大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();

        System.out.println("maxMemory="+maxMemory+"(字节)、"+(maxMemory/1024/(double)1024)+"MB");
        System.out.println("totalMemory="+totalMemory+"(字节)、"+(totalMemory/1024/(double)1024)+"MB");

    }
}

oom


/*
 * -Xmx8m -Xms8m -XX:+PrintGCDetails
 *
 * 分析GC日志:
 *
 *  [Times: user=0.00 sys=0.00, real=0.00 secs]
 * 1、GC 类型  GC:普通的GC,Full GC :重GC
 * 2、1536K 执行 GC之前的大小
 * 3、504K  执行 GC之后的大小
 * 4、(2048K) young 的total大小
 * 5、0.0012643 secs 清理的时间
 * 6、user 总计GC所占用CPU的时间   sys OS调用等待的时间   real 应用暂停的时间
 *
 * GC :串行执行 STW(Stop The World)  并行执行   G1
*/

public class Demo02 {
    public static void main(String[] args) {
        System.gc(); // 手动唤醒GC(),等待cpu的调用
        String str = "ilovecoding";
        while (true){
            str += str
                    + new Random().nextInt(999999999)
                    + new Random().nextInt(999999999);
        }
        // 出现问题:java.lang.OutOfMemoryError: Java heap space
    }
}

java.lang.StackOverflowError

public static void main(String[] args) {
    a();
}

public static void a(){
    a();
}

java.lang.OutOfMemoryError: Java heap space

// -Xms10m -Xmx10m
public class OomDemo {
    public static void main(String[] args) {
        String str = "Coding";
        while (true){
            str += str + new Random(1111111111) +  new Random(1111111111);
        }
    }
}

java.lang.OutOfMemoryError: GC overhead limit exceeded

GC 回收时间过长也会导致 OOM;

可能CPU占用率一直是100%,GC 但是没有什么效果!

// -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
public class OomDemo {
    public static void main(String[] args) throws Throwable {
        int i = 0;
        List<String> list = new ArrayList<String>();

        try {
            while (true){
                list.add(String.valueOf(++i).intern());
            }
        } catch (Throwable e) {
            System.out.println("i=>"+i);
            e.printStackTrace();
            throw e;
        }
    }
}



java.lang.OutOfMemoryError: Direct buffer memory 基础缓冲区的错误!

// -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
public class OomDemo {
    public static void main(String[] args) throws Throwable {
        System.out.println("配置的MaxDirectMemorySize"
        +VM.maxDirectMemory()/(double)1024/1024+"MB");

        TimeUnit.SECONDS.sleep(2);
        
        // 故意破坏!
        // ByteBuffer.allocate(); 分配JVM的堆内存,属于GC管辖
        // ByteBuffer.allocateDirect() ; // 分配本地OS内存,不属于GC管辖
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
        // java.lang.OutOfMemoryError: Direct buffer memory
    }
}

java.lang.OutOfMemoryError: unable to create native Thread

高并发 , unable to create native Thread这个错误更多的时候和平台有关!

1、应用创建的线程太多!

2、服务器不允许你创建这么多线程!

public class TDemo {
    public static void main(String[] args) {
        for (int i = 1; ; i++) {
            System.out.println("i=>"+i);
            new Thread(()->{
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },""+i).start();
        }
    }
}

Exception in thread “main” java.lang.OutOfMemoryError: unable to create new native thread

1、服务器线程不够了,超过了限制,也会爆出OOM异常!

java.lang.OutOfMemoryError: Metaspace 元空间报错

java8 之后使用元空间代替永久代;本地内存!

1、虚拟机加载类信息

2、常量池

3、静态变量

4、编译后的代码

模拟元空间溢出、不断的生成类即可!

// -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
public class OomDemo {
    
    static class OOMTest{}

    public static void main(String[] args) throws Throwable {

        int i = 0; // 模拟计数器

        try {
            while (true){
                i++;
                // 不断的加载对象! Spring的 cglib;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMTest.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return method.invoke(o,args);
                    }
                });
                enhancer.create();
            }
        } catch (Throwable e) {
            System.out.println("i=>"+i);
            e.printStackTrace();
        }
        
    }
}

Dump内存快照

在java程序运行的时候,想测试运行的情况!

使用一些工具来查看;

1、Jconsole

2、idea debug

3、Eclipse(MAT插件)

4、IDEA(Jprofiler插件)

你常用的项目,发布后配置过JVM 调优参数吗?

java三种类型: 标配参数,x参数,xx参数
运行时
jps -l 看运行进程版本号

jinfo -flag 看某个运行程序某个参数是否开启

-Xms 初始堆大小

-Xmx 最大堆大小

-Xss : 线程栈大小设置,默认 512k~1024k

-Xmn 设置年轻代的大小,一般不用动!

-XX:MetaspsaceSize 设置元空间的大小,这个在本地内存中!

-XX:+PrintGCDetails

-XX:SurvivorRatio

设置新生代中的 s0/s1 空间的比例;

uintx SurvivorRatio = 8 Eden:s0:s1 = 8:1:1

uintx SurvivorRatio = 4 Eden:s0:s1 = 4:1:1

-XX:NewRatio

设置年轻代与老年代的占比:

NewRatio = 2 新生代1,老年代是2,默认新生代整个堆的 1/3;

NewRatio = 4 新生代1,老年代+是4,默认新生代整个堆的 1/5;

-XX:MaxTenuringThreshold

进入老年区的存活阈值;

MaxTenuringThreshold = 15

java -XX:+PrintFlagsInitial 可看默认参数

GC详解

主要调堆
口诀:关于垃圾回收:分代收集算法 不同的区域使用不同的算法

Young代: GC频繁区域

Old代:GC次数较少

Perm代:不会产生GC!

在这里插入图片描述JVM在进行GC时,并非每次都是对三个区域进行扫描的!大部分的时候都是指的新生代!

两个类型:

普通GC:只针对新生代 【GC】

全局GC:主要是针对老年代,偶尔伴随新生代! 【Full GC】

GC四大算法

引用计数法 (了解即可!)

在这里插入图片描述

特点:每个对象都有一个引用计数器,每当对象被引用一次,计数器就+1,如果引用失效,则计数器-1,如果为0,则GC可以清理;

缺点:

  • 计数器维护麻烦!
  • 循环引用无法处理!

JVM 一般不采用这种方式

闲聊:现在一般使用可达性算法,GC Root…

复制算法

年轻代中,就是使用的复制算法!

在这里插入图片描述

1、一般普通GC 之后,差不多Eden几乎都是空的了!

2、每次存活的对象,都会被从 from 区和 Eden区等复制到 to区,from 和 to 会发生一次交换;记住一个点就好,谁空谁是to,每当幸存一次,就会导致这个对象的年龄+1;如果这个年龄值大于15(默认值,后面我们会讲解调整),就会进入养老区!

在这里插入图片描述

优点:没有标记和清除的过程!效率高!没有内存碎片!

缺点:需要浪费双倍的空间

Eden 区,对象存活率极低! 统计:99% 对象都会在使用一次之后,引用失效!推荐使用 复制算法

标记清除算法

老年代一般使用这个,但是会和我们后面的整理压缩一起使用!

在这里插入图片描述

优点:不需要额外的空间!

缺点:两次扫描,耗时较为严重,会产生内存碎片,不连续!

标记清除压缩

在这里插入图片描述

减少了上面标记清除的缺点:没有内存碎片!但是耗时可能也较为严重!

那我们什么时候可以考虑使用这个算法呢?

在我们这个要使用算法的空间中,假设这个空间中很少,不经常发生GC,那么可以考虑使用这个算法!

小总结

内存效率:复制算法 > 标记清除算法 > 标记整理(时间复杂度!)

内存整齐度:复制算法=标记整理>标记清除算法

内存利用率:标记整理 = 标记清除算法 > 复制算法

从效率来说,复制算法最好,但是空间浪费较多!为了兼顾所有指标,标记整理会平滑一点,但是效率不尽人意!

年轻代:

相对于老年区,对象存活率低!

Eden 区,对象存活率极低! 统计:99% 对象都会在使用一次之后,引用失效!推荐使用 复制算法

老年代:

区域比较大,对象存活率较高!

推荐使用:标记清除压缩!

JVM 垃圾回收的时候如何确定垃圾,GC Roots?

什么是垃圾:简单地说,就是不在被引用的对象!

Person person = null;

如果我们要进行垃圾回收,第一步:判断这个对象是否可以回收!

引用计数法!(上面已经谈过)

Java中,引用和对象都是有关联的,如果要操作对象,就要通过引用进行;

可达性分析算法!

在这里插入图片描述

一切都是从 GC Root 这个对象开始遍历的,只要在这里面的就不是垃圾!

什么是Gc Root,(4种)

1、虚拟机栈中引用的对象!

2、类中静态属性引用的对象

3、方法区中的常量

4、本地方法栈中 Native 方法引用的对象!

public class GCRoots{
		
    // private byte[] array = new byte[100*1024*1024]; // 开辟内空间!
    // private static GCRoots2 t2; // GC root;
    // private static final GCRoots3 t3 = new GCRoots3(); // GC root;
    
    // 引用远远不止于此,强引用,软引用,弱引用,虚引用! 四个类的使用!
    public static void m1(){
        GCRoots g1 = new GCRoots(); //GCroot
        System.gc();
    }
    
    public static void main(String[] args){
        m1();
    }

}

谈谈G1垃圾回收器

以前的垃圾回收器的特点

1、年轻代和老年代是各自独立的内存区域

2、年轻代使用 eden+s0+s1 复制算法

3、老年代手机必须扫描整个老年代的区域;

4、垃圾回收器原则:尽可能少而快的执行GC为设计原则!

G1垃圾回收器

G1(Garbage-First)收集器 ,面向服务器端的应用的收集器;

在这里插入图片描述

如何使用

-XX:+UseG1GC

这个还不是它最大的亮点,它增加了一些参数,可以自定义垃圾回收的时间!

-XX:MaxGCPauseMillis=100 最大的GC停顿时间单位:毫秒,JVM尽可能的保证停顿小于这个时间!

G1 优点

1、没有内存碎片!

2、可以精准空垃圾回收时间!

这个jvm蛮详细的https://blog.youkuaiyun.com/TZ845195485/article/details/93238857?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158401791019724846438483%2522%252C%2522scm%2522%253A%252220140713.130056874…%2522%257D&request_id=158401791019724846438483&biz_id=0&utm_source=distribute.pc_search_result.none-task

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值