大厂高频面试题复习JAVA学习笔记-JVM+GC解析

目录

一、JVM内存结构

1.1内存结构

1.2五个分区的功能: 

1.3常见OOM错误 

1.4JMM(Java内存模型)

1.5类加载器

二、JVM垃圾回收

2.1四种GC算法

2.2GC roots 可达性分析

三、 JVM调优

3.1JVM参数类型

 3.2如何查看JVM的一些默认系统值

3.3常用JVM基本配置参数

四、强软弱虚四大引用

4.1整体架构

4.2强引用

4.3软引用

4.4弱引用

4.5WeakHashMap

4.6虚引用

 五、OOM

六、垃圾回收器

6.1四种垃圾回收器

6.2生产中怎么配置GC?

6.3七种GC用法:

6.4G1回收器

6.5GC结合SpringBoot生产部署和调参优化 

七、生产环境中的性能监控

7.1常用Linux命令性能评估

7.2cpu过高故障排查思路

7.3 github操作-工作中如何高效学习


一、JVM内存结构

1.1内存结构

  • 堆(Heap):线程共享。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。
  • 方法区(Method Area):线程共享。存储类信息、常量、静态变量、即时编译器编译后的代码。
  • 方法栈(JVM Stack):线程私有。存储局部变量表、操作栈、动态链接、方法出口,对象指针。
  • 本地方法栈(Native Method Stack):线程私有。为虚拟机使用到的Native 方法服务。如Java使用c或者c++编写的接口服务时,代码在此区运行。
  • 程序计数器(Program Counter Register):线程私有。有些文章也翻译成PC寄存器(PC Register),同一个东西。它可以看作是当前线程所执行的字节码的行号指示器。指向下一条要执行的指令

1.2五个分区的功能: 

  • 堆:
  1. GC的主要操作区域
  2. -Xms设置堆的最小空间大小。-Xmx设置堆的最大空间大小。-XX:NewSize设置新生代最小空间大小。-XX:MaxNewSize设置新生代最小空间大小。
  3. 存放实例和数组,结构上分为新生代和老年代,新生代又分为Eden区和两个survivor区,注意两个区没有先后关系,可以从s0传递到s1也可以从s1传递到s0,两个区总有一个是空的。
  4. 一般情况下,新生代占1/3,老年代占2/3。然后伊甸园区占8/10,幸存区占2/10,from和to区各占1/2。
  5. 堆是线程共享的
  • 方法区:
  1. 是线程共享的区域,和heap一样,又称永久代Permanent Generation
  2. 它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  3. -XX:PermSize 设置最小空间 -XX:MaxPermSize 设置最大空间。
  • 虚拟机栈:
  1. 每一个线程都私有的栈,存放线程中方法调用的栈帧,存放各种数据类型、对象引用等信息
  2. -Xss控制每个线程栈的大小。
  3. - StackOverflowError: 异常线程请求的栈深度大于虚拟机所允许的深度时抛出;
  4. - OutOfMemoryError 异常: 虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出。
  • 本地方法栈:
  1. 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
  2. 与jvm栈一样,抛出的异常是一样的,-Xss配置也是一样的
  • 程序计数器:
  1. 每一个线程都私有的,相当于一个为了控制当前线程所执行的字节码的行动指示器
  2. 是jvm中唯一一个没有OOM的区域

1.3常见OOM错误 

Exception in thread “main”: java.lang.OutOfMemoryError: Java heap space
Exception in thread “main”: java.lang.OutOfMemoryError: PermGen space
Exception in thread “main”: java.lang.OutOfMemoryError: <reason> <stack trace>(Native method)
Exception in thread “main”: java.lang.OutOfMemoryError: Requested array size exceeds VM limit

1.4JMM(Java内存模型)

JMM控制java线程间通讯规则,是一个虚拟的概念,他规定了线程之间的共享变量存在主存中,每一个线程都是有一个私有的本地内存来存储主存中的数据副本,每次对主存数据操作都是先复制到本地内存中修改再返回提交给主内存。

1.5类加载器

  •  概念:

.java文件存放了代码逻辑文件,被编译后形成.class字节码文件,字节码文件存放着JVM所能理解的底层指令,当需要调用某个类时就会加载它的class文件创建class对象,将字节码文件加载到jvm内存,这个过程就是类加载

  • 过程:

  1. 加载:通过类的完全限定查找此类字节码文件,创建class对象
  2. 验证:确保class文件是合法的,包括文件格式、元数据、字节码、符号引用四方面的验证
  3. 准备:既初始化工作,为类变量分配内存和赋初值,一般为0或者null;注意final修饰的static变量是编译阶段就分配了,不在这一阶段初始化;,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
  4. 解析:主要将常量池中的符号引用替换为直接引用的过程。(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入Java虚拟机》)。
  5. 初始化:为前面的static变量赋初值、为成员变量赋初值;
  • 三种类加载器

在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)

  1. Bootstrap:启动类加载器,加载JVM自身的类,主要是各种jar包,它从<JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,而且Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
  2. Extension:是sun实现的sun.misc.Launcher$ExtClassLoader类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,是一个静态内部类,用于开发者直接使用
  3. System:是指 Sun公司实现的sun.misc.Launcher$AppClassLoader,它负责加载系统类路径java -classpath-D java.class.path 指定路径下的类库,是我们经常用到的classpath路径,一般情况下该类加载是程序中默认的类加载器,ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。
  • 双亲委派机制

如果一个类收到了类加载的请求,他首先会提交给父类的加载器执行,如此一步步向上委托,如果父类能加载就加载,不能加载才会让子类去加载,他有以下作用:

  1. 避免类的重复加载,如果父类已经加载过这个类,子类就没必要再加载一遍;
  2. 防止核心API库被篡改,如果某个类通过双亲委派传递到启动类加载器,而核心API中发现有这个类,这样会直接返回已经被加载的api类,不会再去加载一遍。

二、JVM垃圾回收

2.1四种GC算法

  • 引用计数算法
  • 复制算法

  • 标记清除算法

  • 标记整理算法

  • 分代回收算法

是指在新生代和老年代采取不同的回收算法,新生代用复制算法,老年代用标记整理或者标记清除算法;

  1. 一个对象到了堆里面,如果Eden区有容量就直接放在Eden区
  2. 如果Eden区不够放就会就进行一次minorGC,把Eden区的对象年龄加一,放到s0区
  3. 如果s0不够,就进行一次minorGC,把s0、Eden区的对象年龄加一,放到s1区
  4. s0和s1区的内容会一直如此反复,s0+eden=s1,s1+Eden=s0,所以一直有一块是空
  5. 因为每次复制年龄都会加一,到了15时对象就会自动传递到老年区
  6. 如果再来一个对象连老年区都不够放,就会先minorGC看看Eden区够不够,如果不够就fullGC;
  7. full GC 会将新生代和老年代都会进行回收,并且会使其他的进程全部停止,称为stop the word(STW),就是平常的卡顿,如果进行full GC 之后,内存还是不够的时候,就会抛出异常OOM。
  8. 如果一开始直接来了个大对象,新生代放不下会直接去老年代

2.2GC roots 可达性分析

  • 如何判断对象是不是垃圾(可达性分析):

枚举根节点做可达性分析比如上图,对象C就是对象不可达,是垃圾。

  • GCroot包括
  1. 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
  2. 方法区中的类静态属性引用的对象
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(Native方法)引用的对象

三、 JVM调优

3.1JVM参数类型

JVM有三种参数类型,标配参数、-X参数、-XX参数

  • 标配参数

从jdk1.0到现在,都没变的命令:

java -version
java -help
java - showversion
  • X参数
-Xint
-Xcomp
-Xmixed

  • XX参数

又分为布尔类型和键值类型:

1、Boolean类型:

-XX:+或者-某个属性(+代表开启,-代表关闭)

终端使用jps -l命令查看当前进程,使用jinfo -flag查看是否开启-XX参数:

-XX:PrintGCDetails

使用-XX参数:

2、KV设值类型:

  • 元空间大小和新生代对象年龄阈值设置:

配置metaspacesize:

  • 参数显示:jinfo -flags 进程号

其中Non-default是默认的,Command line是人工新加的:

两个经典参数-Xms和-Xmx,这两个参数是-XX类型的。

 3.2如何查看JVM的一些默认系统值

可以通过以下三种方法罗列: 

  1. -XX:+PrintFlagsinitia
  2. -XX:+PrintflagsFinal          PrintFlagsFinal举例,运行java命令的同时打印出参数
  3. -XX:+PrintCommandLineFlags
  • -XX:+PrintFlagsinitia:显示所有jvm参数:

  • -XX:+PrintflagsFinal   :查看修改更新的值

等号前面有冒号代表被修改过的参数:

T是java运行类名,-XX:+PrintflagsFinal 可以运行时修改jvm参数:

 

  • -XX:+PrintCommandLineFlags:可以看见命令行-XX参数,展示的最后一个参数是并行GC回收器

3.3常用JVM基本配置参数

  •  java8新特性:
  • 获取运行时初始内存大小:

这是一个内存16G的电脑设备,可见默认-xms是1/64倍物理内存大小,-xmx是1/4倍物理内存大小

  •  常用参数:

  • -Xms:等价于-XX:InitialHeapsize
  • -Xmx:等价于-XX:MaxHeapsize
  • -Xss:等价于-XX:ThreadStackSize,设置jvm栈大小;

下图演示了一个简单线程中,默认大小是0:

官网解释:

最后手动配置-Xss128k  结果:

  • -Xmn:设置年轻代的大小
  • -XX:MetaspaceSize:设置元空间大小(java8后元空间放在本地物理内存中,不在jvm中,所有受本地内存大小限制),默认才21M,所以一般生产中会设置大一点,要不然容易OOM

经典配置参考,给出了配置前和配置后jvm参数的变化,一般也就配这些参数:

  • -XX:+PrintGCDetails:打印GC细节

案例:只分配10m的堆空间,但是建立一个对象为50m的数组:

可见控制台不仅报错,还给出了GC日志:

9728k是堆大小,大概10M,2560k是新生代大小,占三分之一,2048k=2560*0.8,满足1:1:8的新生代内部分区规则,最后fullGC都没能解决,直接报错OOM!!!

GC日志解析:

  • -XX:SurvivorRatio

  • -XX:NewRatio

默认情况:

  • -XX:MaxTenuringThreshold

设置垃圾最大年龄,默认15

  1. 默认某对象实行GC复制算法最多15次,就不能呆在新生代了,要搬家去养老代。
  2. -XX:MaxTenuringThreshold=0:设置垃圾最大年龄为0的话,则年轻代对象不经过Surivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
  3. 而且只能在0-15内调:

四、强软弱虚四大引用

4.1整体架构

Reference强、SoftReference软、WeakReference弱、PhantomReference虚

4.2强引用

  • 当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
  • 强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在 java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即体该对象以后永远都不会被用到,M也不会回收。因此强引出是造成,ava内存泄漏的主要原因之一。
  • 对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 nul,一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。
  • 案例:

程序正常运行,0bj2是强引用,不会被回收,obj1置空所以被回收

4.3软引用

  • 软引用是一种相对强引用弱化了一些的引用,需要用java.ang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
  • 对于只有软引用的对象来说,当系统内存充足时它不会被回收,当系统内存不足时它会被回收。
  • 软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!像mybatis缓存代码一些内部类就是以此实现。
  • 案例:

内存够用,所以不会被回收

内存不够用,报错oom:

4.4弱引用

不管内存够不够,一律被回收:

一般软弱引用用于缓存:

4.5WeakHashMap

就是实现了虚引用功能的hashmap,可见GC后hashmap被置空:

4.6虚引用

  1. 虚引用需要java.lang.ref.PhantomReference类来实现。
  2. 顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收;它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
  3. 虚引用的主要作用是跟踪对象被垃圾回收的状态。 仅仅是提供了一种确保对象被 finalize以后,做某些事情的机制。PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入fnalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
  4. 换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。
  5. Java 技术允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

虚引用案例:把弱引用放在一个队列里:

执行gc后,引用都被回收,但是虚引用会输出引用队列的信息:

虚引用;类似于后置通知:

 五、OOM

  • java.lang.StackOverflowError

OOM和栈溢出都是错误,最常见的就是方法循环调用

  • java.lang.OutOfMemoryError:Java heap space

对象占用堆空间过头:

  • java.lang.OutOfMemoryError: GC overhead limit exceeded

GC一直运行:

案例:

全是fullGC,十四万次i:

可见GC每个区回收前后都是一样的,

  • java.lang.OutOfMemoryError: Direct buffer memory

一般NIO操作会出现这种OOM:

  • java.lang.OutOfMemoryError: unable to create new native thread

高并发场景,不能创建新的本地线程:

  • java.lang.OutOfMemoryError: Metaspace

六、垃圾回收器

6.1四种垃圾回收器

  • serial串行回收器

在单线程环境中,只有一个gc线程,执行gc时候会暂停用户线程,不适合服务器环境

  • parallel并行垃圾回收器

有多个gc线程执行垃圾回收, 用户线程也会暂停,适用于科学计算和大数据处理首台处理等弱交互场景

  • CMS并发

用户线程和gc、可以同时执行,适用于强交互场景,但也会有内存碎片问题

  • G1回收器

java8诞生,将堆内存分为不同区域,并发进行垃圾回收

6.2生产中怎么配置GC?

  •  如何查看默认GC:
-XX:PrintCommandLineFlags -version

  •  默认GC有哪些?
  1. UseSerialGc,一对一
  2. UseParallelGc,多对多
  3. UseiconcMarkSweepGc,(CMS)
  4. UseParNewGc,多对一
  5. UseParallelOldGc,
  6. UseG1Gc
  • 垃圾收集器在不同分区的使用

6.3七种GC用法:

SerialOld现在已经废弃,一般配完新生代,老年代也会自动配配置对应GC:

参数说明:

Server和Client模式:

  • 新生代

1、Serial:养老区默认SerialOld

2、Parnew:养老区默认CMS

3、parallelGC:

  • 老年代

4、parallelOld:年轻代自动也是并行收集

5、CMS: 年轻代自动parnew

CMS有四个过程:

  1. 初始标记(cMs initial mark)
  2. 并发标记(CMS concurrent mark)和用户线程一起
  3. 重新标记(CMs remark)
  4. 并发清除(CMS concurrent sweep)和用户线程一起

CMS优点是并发收集停顿低,缺点是cpu负担大。由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间

6、serialOld:

6.4G1回收器

  • 概念:

配置G1后,不分新生老年代,

从官网的描述中,我们知道G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。另外,它还具有以下特性:

  1. 像CMS收集器一样,能与应用程序线程并发执行。
  2. 整理空闲空间更快。
  3. 需要更多的时间来预测GC停顿时间。
  4. 不希望牺牲大量的吞吐性能。
  5. 不需要更大的Java Heap。

G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:

G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。

G1的Stop The Word(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。

1:G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW。

2:G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片。

3:宏观上看G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域(Region),可以近似理解为一个围棋的棋盘。

4:G1收集器里面讲整个的内存区都混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离的,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。

5:G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换:

  • G1底层原理:

 区域化内存划片Region,整体编为了一些列不连续的内存区域,避免了全内存区的GC操作。

核心思想是将整个堆内存区域分成大小相同的子区域(Regjon),在JVM启动时会自动设置这些子区域的大小。

在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。

启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为:32MB*2048=65536MB=64G内存

过程演示:

回收步骤:

  • GC如何选型:
  • 单CPU或小内存,单机程序-XX:+UseSerialGc
  • 多CPU,需要最大吞吐量,如后台计算型应用
  1. -XX:+UseParallelGc
  2. -XX:+UseParalleloldGc
  • 多CPU,追求低停顿时间,需快速响应如互联网应用
  1. -XX:+UseConcMarkSweepGc
  2. -XX:+ParewGc

6.5GC结合SpringBoot生产部署和调参优化 

七、生产环境中的性能监控

7.1常用Linux命令性能评估

  • top命令,主要查看PID对应的CPU和内存MEM:

load average系统负载均衡,分别代表一分钟、五分钟、十五分钟的平均负载值,如果三个数的平均值高于60%,一般就认为系统压力过大:

  • uptime命令,是top的精简版:

  • vmstat:

每两秒采样一次,共采样三次:

  • mpstat -P ALL 2:查看所有cpu信息:   

  • pidstat -u 1 -p:查看每个进程使用cpu的用量分解信息:
  •  free:查看内存:

  • pidstat -p 进程号 -r 采样时间间隔:

  •  df查看磁盘空间:

  • iostat磁盘IO:

 

  • 也可以用pidstat:

  • ifstat:网络IO

 

7.2cpu过高故障排查思路

 

前两步很简单不解释,第三步:定位线程TID

第四步、转换十六进制:5102->13ee

五:打印前六十行日志:

找到是第十行代码出错:

7.3 github操作-工作中如何高效学习

一般工作中实现一个功能并不是从头开始,站在巨人肩膀上能看的更远,套用优秀框架能帮我们更好的工作和学习,所以要熟练使用GitHub,以下展示了一些常用github操作帮助自己更高效的学习优秀开源框架。

  • 基本功能:

  • 进阶操作:in关键字组合查询

比如查一个秒杀功能:

  • star和fork:

eg:

组合查询:

  • awesome命令:

优秀框架,一般用于学习:(redis正常查询会有四万多条记录,但awesome后只有四十多条)

  • #L

网页地址后面#L加数字,可以高亮代码提示重要代码,方便程序员之间交流

  • github快捷键:

一般常用t快捷键

  •  查看大佬:

创建引用的时候可以指定关联的队列,当GC释放对象内存的时候,会将引用加入到引用队列,如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动这相当于是一种通知机制。

学习视频来自尚硅谷,笔记为个人学习总结,新手上路多多包涵~~~ 

80_GCRoots和四大引用小总结_哔哩哔哩_bilibili

  • 11
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值