JVM的相关应用和原理解析

《JAVA中的基本数据类型和引用基本类型区别》涉及到的JVM

静态代码块的详细执行过程

Java自带的bin目录下的 jconsole.exe工具可以查看Tomcat的默认垃圾回收器

一、内存结构

1.1、Java内存分配图

JVM的相关应用和原理解析_第1张图片

1.2、方法区

方法区是一个规范,他的实现在JDK1.7之前是叫“永久代”,JDK1.8后叫“元空间”。

作用:用来存储类对象,类加载器,静态变量,StringTable,SymbolTable,即时编译器生成的代码等。

1.3、虚拟机栈(线程私有)

1.3.1、作用:

1.3.1.1、栈的大小 = 一个线程使用的内存大小

1.3.1.2、一个线程调用方法一次产生一个“栈帧”,一个“栈帧”包括方法的参数、局部变量等。多个“栈帧”成为“栈”。而正在执行的方法称为“活动栈帧”,一个线程内同一时刻只能有一个“活动栈帧”。

1.3.2、具体设置参数

-Xss
The default value depends on the platform:

  • Linux/x64 (64-bit): 1024 KB
  • macOS (64-bit): 1024 KB
  • Oracle Solaris/x64 (64-bit): 1024 KB
  • Windows: The default value depends on virtual memory

例如:-Xss2M
JVM的相关应用和原理解析_第2张图片

1.3.3、特点

1.3.3.1、方法执行完,“栈帧”就被立即释放。

1.3.3.2、因为线程私有,不存在共享,所以线程安全

1.3.4、什么情况下会使栈空间溢出?

1.3.4.1、方法的嵌套很深,一直到栈内存溢出(栈帧太多)

1.3.4.2、方法内局部变量太多(栈帧太大)

1.3.5、具体例子如下:

public class Demo1 {

    private static  int count = 0;

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

    private static void method1() {
        count ++ ;
        System.out.println(count);
        method1();
    }

}

1.4、本地方法栈(线程私有)

java代码中,被native修饰的方法,也就是C++运行的程序。

JVM的相关应用和原理解析_第3张图片

1.5、程序计数器(线程私有)

记录每个线程的执行到的记录,每个计数器对应一个线程,即是线程私有的。

线程要轮流使用 CPU 时间片,因此需要『程序计数器』来记住正在执行的字节码的地址。例如 线程 A 的计数器记录当前执行到了第三行字节码,这时候时间片用完了,CPU 切换到其它线程运行,当 CPU 再次切换到 线程 A 时,它就会从计数器得知上次执行的代码位置,继续向下运行。

JVM的相关应用和原理解析_第4张图片

1.6、堆空间(线程共享)

堆空间分为新生代和老年代;新生代分为:伊甸园区、幸存区(from区和to区)。堆空间主要是创建对象时所使用的空间。

1.6.1、什么情况会导致堆空间溢出?

当创建的对象足够大时,也就是一直循环使对象不断增大。

1.6.2、具体例子如下:

	@Test
    public void testHeap(){
        List<String> l = new ArrayList<String>();
        String s = "abc";
        String ss = "123";
        while (true){
            s+=ss;
            l.add(s);
        }
    }

1.6.3、堆内存的设置

-Xms #最小堆内存
-Xmx #最大堆内存 一般最大值和最小值设置相同,不会因为内存重新分配,造成性能浪费。

1.7、Tomcat中的Java内存分配图

幸存区是总共有两块,但是其中一块是不占空间的。

JVM的相关应用和原理解析_第5张图片

二、垃圾回收(回收的是堆内存的数据;栈因为使用完后,自己立即消失,不需要回收。)

2.1、判断对象是否是垃圾的两种方式:

2.1.1、第一种方式:引用计数法

某个地方引用对象一次,就记录引用次数为一次;当引用次数为0时,就进行垃圾回收。
缺点是:难以解决对象之间的循环引用问题

2.1.2、第二种方式:可达性分析算法

2.1.2.1、从GC Roots对象开始,对所有引用对象边探索边标记;然后根据GC Root开始,沿着引用链去查找没有被标记的引用对象并收回。

GC Root对象: 包括栈帧中的局部变量、方法区中的静态变量、方法区中的常量、本地方法栈中JNI引用的对象。

注意:在进行标记的过程中,防止发生堆栈变化(内存溢出或者触发垃圾回收机制),Java 虚拟机采取安全点机制来实现 Stop-the-world (应用程序的线程全部停止) 操作,这个操作只是暂时性的,等垃圾回收线程执行完后恢复。

2.2、垃圾回收算法

2.2.1、标记清除(两个过程标记和清除)

第一遍标记、第二遍收集。缺点是会产生内存碎片,碎片过多,仍会使得连续空间少。
JVM的相关应用和原理解析_第6张图片

2.2.2、标记整理

第一遍标记、第二遍整理,整理是指存活对象向一端移动来减少内存碎片,缺点是:整理过程相对效率较低。

JVM的相关应用和原理解析_第7张图片

2.2.3、复制

开辟两份大小相等空间,一份空间始终空着,垃圾回收时,将存活对象拷贝进入空闲空间,优点是不会有内存碎片,没有整理过程。缺点是:占用空间多。

JVM的相关应用和原理解析_第8张图片

2.3、分代垃圾回收

大部分对象存活时间短,小部分对象存活时间比较长。新生代对象一般很少存活,采用【复制算法】、老年代对象生存时间长,适合采用【标记-清除算法】或【标记-整理算法】

新生代内存不足触发的 GC 称为 Minor GC ,暂停时间很短,老年代内存不足触发的 GC 称为 Full GC 暂停时间较长,一般是新生代 GC 的几十倍,

2.3.1、尝试在伊甸园分配

情况一:伊甸园可以存储下新来的对象

JVM的相关应用和原理解析_第9张图片

情况二:伊甸园不能存储下新来的对象,标记可回收的对象(图中用黄色表示),这时候会用户线程会被暂停(Stop The World)。

JVM的相关应用和原理解析_第10张图片
JVM的相关应用和原理解析_第11张图片

触发新生代的垃圾回收,称为 Minor GC ,幸存对象移入『幸存区 To』,注意这里用的是复制算法,因此在幸存区没有碎片。

JVM的相关应用和原理解析_第12张图片

最后的结果,注意 GC 完成后,From 和 To 交换了位置,另外幸存区的对象开始记录寿命。

JVM的相关应用和原理解析_第13张图片

2.3.2、如果是大对象直接晋升至老年代

当对象太大,伊甸园包括幸存区都存放不下时,这时候老年代的连续空间足够,此对象会直接晋升至老年代,不会发生 GC。
JVM的相关应用和原理解析_第14张图片

结果:
JVM的相关应用和原理解析_第15张图片

2.3.3、多次存活的对象

在幸存区历经多次 GC 还存活的对象会晋升至老年代,默认晋升的阈值是 15,,但注意如果目标 survivor 空间紧张,也不必等足 15 次,可以提前晋升。

2.3.4、多次存活的对象老年代连续空间不足,触发 Full GC

2.4、垃圾回收器

minor GC: 在新生代进行的GC

又称新生代GC,指发生在新生代的垃圾收集动作;

因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快;

major GC: 在老年代进行的GC

又称Major GC或老年代GC,指发生在老年代的GC;

Full GC : 同时作用于新生代和老年代

出现Full GC经常会伴随至少一次的Minor GC(不是绝对,Parallel Sacvenge收集器就可以选择设置Major GC策略);

2.4.1、单线程/串行收集器

Serial + SerialOld

1). Serial 工作在新生代的单线程收集器,采用『复制算法』,垃圾回收发生时,会暂停所有用户线程

2). SerialOld 工作在老年代的单线程收集器,采用『标记-整理算法』,垃圾回收发生时,会暂停所有用户线程(stop-the-world)

配置 : 
	-XX:+UseSerialGC

采用单线程执行所有的垃圾回收工作, 适用于单核CPU服务器,无法利用多核硬件的优势
JVM的相关应用和原理解析_第16张图片

2.4.2、多线程回收器-吞吐量优先

1). Parallel Scavenge 工作在新生代的多线程收集器,采用『复制算法』,垃圾回收发生时,会暂停所有用户线程,它的特点是一个以吐量优先 的回收器。

2). Parallel Old 工作在老年代的多线程收集器,采用『标记-整理算法』,垃圾回收发生时,会暂停所有用户线程,也是以 吞吐量优先 的回收器。

配置
	
	-XX:+UseParallelGC
	
	-XX:+UseParallelOldGC

JVM的相关应用和原理解析_第17张图片

2.4.3、多线程回收器-响应时间优先

ParNew + SerialOld + CMS

1). ParNew 工作在新生代的多线程收集器,采用『复制算法』,垃圾回收发生时,会暂停所有用户线程,单核 cpu 并不能工作地比 Serial 好。

2). CMS(Concurrent Mark Sweep)用在重视响应速度,停顿时间最短的场合。工作在老年代,基于多线程和『标记-清除算法』,优点是在标记和清理的某些阶段不必暂停用户线程。

2.4.4、G1收集器

G1(Garbage-First)适用于大内存空间 , 会把整个内存区域划分为大小相等的若干区域(region),分为Eden ,Survivor ,Old ,Humongous 四种类型,G1优先回收其中垃圾最多的区域。它采用的算法是 Mark-Copy 不会产生大量内存碎片,它的优势在于可预测的停顿时间 。
JVM的相关应用和原理解析_第18张图片

配置 : 
	-XX:+UseG1GC

2.4.5、垃圾收集器组合

JVM的相关应用和原理解析_第19张图片

2.5、GC参数

参数 描述
-XX:+UseSerialGC 启用串行收集器
-XX:+UseParallelGC 启用并行垃圾收集器,配置了该选项,那么 -XX:+UseParallelOldGC默认启用
-XX:+UseParallelOldGC FullGC 采用并行收集,默认禁用。如果设置了, -XX:+UseParallelGC则自动启用
-XX:+UseParNewGC 年轻代采用并行收集器,如果设置了 -XX:+UseConcMarkSweepGC选项,自动启用
-XX:ParallelGCThreads 年轻代及老年代垃圾回收使用的线程数。默认值依赖于JVM使用的CPU个数
-XX:+UseConcMarkSweepGC 对于老年代,启用CMS垃圾收集器。 当并行收集器无法满足应用的延迟需求是,推荐使用CMS或G1收集器。
启用该选项后, -XX:+UseParNewGC 自动启用。
-XX:+UseG1GC 启用G1收集器。 G1是服务器类型的收集器, 用于多核、大内存的机器。它在保持高吞吐量的情况下,高概率满足GC暂停时间的目标。

我们也可以在测试的时候,将JVM参数调整之后,将GC的信息打印出来,便于为我们进行参数调整提供依据,具体参数如下:

选项 描述
-XX:+PrintGC 打印每次GC的信息
-XX:+PrintGCApplicationConcurrentTime 打印最后一次暂停之后所经过的时间, 即响应并发执行的时间
-XX:+PrintGCApplicationStoppedTime 打印GC时应用暂停时间
-XX:+PrintGCDateStamps 打印每次GC的日期戳
-XX:+PrintGCDetails 打印每次GC的详细信息
-XX:+PrintGCTaskTimeStamps 打印每个GC工作线程任务的时间戳
-XX:+PrintGCTimeStamps 打印每次GC的时间戳

如果是在Tomcat中运行 , 需要在bin/catalina.sh的脚本中 , 追加如下配置 :

JAVA_OPTS="-XX:+UseConcMarkSweepGC  -XX:+PrintGCDetails"

Windows

set JAVA_OPTS=-server  -Xms2048m  -Xmx2048m  -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:SurvivorRatio=8

八、栈和堆

栈是先进后出,而队列是先进先出

九、数组的内存分配(堆内存里执行的)

8.1、单个数组内存图

赋值:

数组名[索引值] = 对应类型的值;

8.2、多个数组内存图

arr遥控001电视机
arr1遥控002电视机
各自互不影响。

8.3、多个数组指向相同内存图

arr arr2两个遥控器同时控制一台电视机。
如果一个遥控器arr多台电视(内存图),就看arr最后遥控的那一台内存图。

你可能感兴趣的:(笔记)