JVM
🥋

JVM

Created
Jun 17, 2021 03:40 PM
Tags
Java

1. JVM内存区域

JVM内存主要可以分为五个区域:程序计数器、虚拟机栈、本地方法栈、堆、方法区。其中除了堆和方法区是线程共有的,其他几个区域都是线程私有的:
1、程序计数器
程序计数器是一块较小的内存,通过这个计数器来选取下一条需要执行的字节码指令。Java虚拟机的多线程是通过线程之间切换来轮流获得处理器的执行时间的,每个线程都有自己独立的程序计数器,它们互不影响,也就是线程私有的。如果执行的是Java方法,这里存放的是指令地址,如果执行的是Native方法,这里的值为空(Undefined)。并且这个区域不会出现OutOfMemoryError
2、虚拟机栈
虚拟机栈也是线程私有的,虚拟机栈描述的是Java方法执行的内存模型,执行每个方法的时候都会创建一个栈帧,用来存储局部变量表、操作数栈、方法出口等信息。局部变量表中存放了各种基本数据类型,对象引用类型,局部变量表在编译期间完成分配。
这个区域有两种异常情况:如果线程请求的栈深度大于虚拟机容许的最大深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,但是扩展时无法申请到足够的内存,抛出OutOfMemoryError异常。
3、本地方法栈
本地方法栈与虚拟机栈的作用类似,只不过是虚拟机栈是为执行Java方法服务,而本地方法栈是为执行Native方法服务。
4、堆
Java堆是由所有线程共享的一块内存区域,在虚拟机启动时创建,并且也是Java虚拟机管理管理的内存中最大的一块。Java堆主要用于存放对象实例以及数组,Java堆是垃圾回收的主要区域,也被成为“GC”堆。关于垃圾回收在下一篇博客中我会详细介绍。
5、方法区
方法区也是各个线程共享的内存区域,用于存储已经被虚拟机加载的类信息、常量、静态变量等数据。这个区域也会存在垃圾回收,不过回收的目标主要是针对常量池和对类型的卸载,不过由于类型的卸载条件比较苛刻,所以回收成绩难以令人满意。
里面还介绍了对象访问的两种方式, 使用句柄和使用直接指针, HotSpot虚拟机使用的是直接指针

2. 垃圾回收机制

垃圾回收算法: 引用计数, 分代回收, 复制算法, 标记-清除, 标记-整理
垃圾回收的区域: GC主要发生在堆区和方法区

2.1 垃圾回收的类别

  • Minor GC
  • Minor GC指新生代GC,即发生在新生代(包括Eden区和Survivor区)的垃圾回收操作,当新生代无法为新生对象分配内存空间的时候,会触发Minor GC。因为新生代中大多数对象的生命周期都很短,所以发生Minor GC的频率很高,虽然它会触发stop-the-world,但是它的回收速度很快。
notion image
  • Major GC
  • Major GC清理Tenured区,用于回收老年代,出现Major GC通常会出现至少一次Minor GC。
  • Full GC
  • Full GC是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。Full GC不等于Major GC,也不等于Minor GC+Major GC,发生Full GC需要看使用了什么垃圾收集器组合,才能解释是什么样的垃圾回收。

2.2 如何判断对象能不能被回收

枚举根节点做可达性分析(为了解决引用计数法的循环引用问题):
GC roots是一组必须活跃的引用, 通过一系列GC roots对象作为起点, 从这个被称为GC Roots的对象开始向下搜索, 如果一个对象到GC Roots没有任何引用链相连时, 则说明此对象不可用. 即给定一个集合的引用作为根出发, 通过引用关系遍历对象图, 能被遍历到的(可达的)对象就被判定为存活, 没有被遍历到的就自然被判定为死亡.
(复制, 标记-清除, 标记-压缩 都使用这一方法来回收垃圾)
notion image
可以作为GC Roots的对象:
  1. 虚拟机栈(局部变量表)中引用的对象
  1. 方法区中的类静态属性引用的对象
  1. 方法区中常量引用的对象
  1. 本地方法栈中JNI(Native方法)引用的对象
令人疑惑的一点, 网上的另一种说法是这样的(其实是一样的, 下面更全面一点, 下面这些也包含上面这四条):
A garbage collection root is an object that is accessible from outside the heap. The following reasons make an object a GC root:
1.System Class ----------Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* .
2.JNI Local ----------Local variable in native code, such as user defined JNI code or JVM internal code.
3.JNI Global ----------Global variable in native code, such as user defined JNI code or JVM internal code. 4.Thread Block ----------Object referred to from a currently active thread block. Thread ----------A started, but not stopped, thread.
5.Busy Monitor ----------Everything that has called wait() or notify() or that is synchronized. For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object.
6.Java Local ----------Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.
7.Native Stack ----------In or out parameters in native code, such as user defined JNI code or JVM internal code. This is often the case as many methods have native parts and the objects handled as method parameters become GC roots. For example, parameters used for file/network I/O methods or reflection.
7.Finalizable ----------An object which is in a queue awaiting its finalizer to be run.
8.Unfinalized ----------An object which has a finalize method, but has not been finalized and is not yet on the finalizer queue.
9.Unreachable ----------An object which is unreachable from any other root, but has been marked as a root by MAT to retain objects which otherwise would not be included in the analysis.
10.Java Stack Frame ----------A Java stack frame, holding local variables. Only generated when the dump is parsed with the preference set to treat Java stack frames as objects.
11.Unknown ----------An object of unknown root type. Some dumps, such as IBM Portable Heap Dump files, do not have root information. For these dumps the MAT parser marks objects which are have no inbound references or are unreachable from any other root as roots of this type. This ensures that MAT retains all the objects in the dump.
GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。
一个对象可以属于多个root,GC root有几下种:
Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots。
Thread - 活着的线程
Stack Local - Java方法的local变量或参数
JNI Local - JNI方法的local变量或参数
JNI Global - 全局JNI引用
Monitor Used - 用于同步的监控对象
Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此需要去确定哪些是属于"JVM持有"的了。
 
感觉这种说法比较有说服力一点

3. JVM参数

3.1 标配参数

  • version -help -showversion

3.2 X参数

  • Xint: 解释执行 -Xcomp: 第一次使用就编译 -Xmixed: 混合模式

3.3 XX参数

  • XX:+/-PrintGCDetails 开启或关闭GC的信息
  • XX:+/-UseSerialGC 是否使用串行垃圾回收器
  • XX:MetaspaceSize=1024m 设置元空间的大小
  • XX:MaxTenuringThreshold=15 设置对象年龄阈值为15
  • Xms1024m 等价于 -XX:InitialHeapSize=1024m 初始化堆内存
  • Xmx1024m 等价于 -XX:MaxHeapSize=1024m 最大堆内存

3.4 常用参数

notion image
http://www.blogjava.net/chenhui7502/archive/2011/08/18/356755.html 关于-Xss是干什么用的 用来存储栈帧信息
notion image

4. 引用

4.1 强引用

强引用就是最常见的普通对象引用, 只要还有强引用指向一个对象, 就表明对象还活着, 垃圾收集器不会碰这种对象, Java中最常见的就是强引用, 把一个对象赋给一个引用变量, 这个引用变量就是一个强引用, 当一个对象被强引用变量引用时, 它处于可达状态, 是不可能被垃圾回收机制回收的, 即使对象以后永远都不会被用到也不会被回收(出现OOM也不会被回收), 因此强引用是造成Java内存泄漏的主要原因之一.
对于一个普通的对象, 如果没有其他的引用关系, 只要超过了引用的作用域或者显式地将相应(强)引用赋值为null, 一般认为就是可以被垃圾收集的了.

4.2 软引用

软引用是一种相对于强引用弱化一些的引用, 使用SoftReference来实现, 对于软引用的对象来说, 当系统内存充足时, 不会被回收, 当系统内存不足时, 会被回收.
软引用通常用在对内存敏感的程序中, 比如高速缓存就有用到软引用, 内存够用就保留,不够用就回收.

4.3 弱引用

弱引用也是用来描述非必需对象的(WeakReference),被弱引用关联的对象 只能生存到下一次垃圾收集发生之前 。当垃圾收集器工作时,无论内存是否足够,都会回收掉只被弱引用关联的对象。
WeakHashMap: 里面的entry在GC之后, 会被回收.

4.4 虚引用

虚引用是最弱的一种引用关系(PhantomReference)。 无法通过虚引用来取得一个对象实例(总是返回null) 。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

4.5 垃圾回收与引用的关系图

notion image

5. OOM

5.1 StackOverflowError

栈溢出错误, 超出了栈调用的最大深度.

5.2 OutOfMemoryError: Java heap space

内存溢出错误, 堆空间溢出

5.3 OutOfMemoryError: GC overhead limit exceeded

GC回收时间过长时抛出的错误, 过长的定义是: 超过98%的时间用来GC并且回收了不到2%的堆内存, 连续多次GC都只回收了不到2%的极端情况下才会抛出.

5.4 OutOfMemoryError: Direct buffer memory

ByteBuffer. allocate(capability)第一种方式是分配java堆内存,属于GC管理范围,由于需要进行拷贝,所以比较慢。
ByteBuffer. allocteDirect (capability)第二种方式是分配操作系统的本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。
但如果不断分配本地内存,堆内存很少使用,那么java虚拟机就不需要进行GC, DirectByteBuffer对象们就不会被回收,这个时候堆内存充足,但本地内存可能已经使用光了,在尝试分配本地内存就会出0ut0fMemory Error

5.5 OutOfMemoryError: unable to create new native thread

这类错误通常发生在应用试图创建新线程时。
可能原因
1. 系统内存耗尽,无法为新线程分配内存
2. 创建线程数超过了操作系统的限制(Linux默认单个进程可以创建的最大线程数为1024个线程)

5.6 OutOfMemoryError: Metaspace

元空间内存溢出.
Java8以及之后的版本使用Metaspace来代替永久代. Metaspace是方法区在HotSpot中的实现.使用的是本地内存.
里面存放了以下信息:
虚拟机加载的类信息,  常量池,  静态变量,  即时编译后的代码.

6. 垃圾回收器

6.1 串行垃圾回收器(Serial)

它为单线程环境设计且只使用一个线程进行垃圾回收, 会暂停所有的用户线程, 所以不适合服务器环境.

6.2 并行垃圾回收器(Parallel)

多个垃圾回收线程并行工作, 也会暂停所有的用户线程, 适合于科学计算/大数据处理/后台处理等弱交互场景

6.3 并发垃圾回收器(CMS)

用户线程和垃圾回收线程同时执行(不一定同时, 也可能交替执行), 不需要停顿用户线程, 互联网公司多用, 适用于对响应时间有要求的场景

6.4 G1垃圾回收器

G1垃圾回收器将堆内存分割成不通的区域然后并发的对其进行垃圾回收