其他分享
首页 > 其他分享> > JVM

JVM

作者:互联网

1 Java虚拟机体系结构

JDK、JRE、JVM

image-20220321145018873

JVM是什么?

image-20220321145114487

JVM内部组成

image-20220321145735881

img

2 类加载机制

什么是类的加载

image-20220321145850073

类加载过程

image-20220321150006702

类加载器的种类

img

image-20220321150112213

双亲委派机制

20201217213314510

双亲委派模式优势

  • 沙箱安全机制:自己写的String.class类不会被加载,这样便可以防止核心API库被随意篡改
  • 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次

沙箱安全机制

Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

所有的Java程序运行都可以指定沙箱,可以定制安全策略。

JDK1.0时期

在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱(Sandbox)机制。如下图所示JDK1.0安全模型:

img

JDK1.1时期

JDK1.0中如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。

因此在后续的Java1.1版本中,针对安全机制做了改进,增加了安全策略。允许用户指定代码对本地资源的访问权限。如下图所示JDK1.1安全模型:

img

JDK1.2时期

在Java1.2版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示JDK1.2安全模型:

img

JDK1.6时期

当前最新的安全机制实现,则引入了域(Domain)的概念

虚拟机会把所有代码加载到不同的系统域和应用域。系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示,最新的安全模型(jdk1.6)

img

3 Java虚拟机运行时内存区域

线程私有区域

image-20220321152441598

PC寄存器

程序计数器:Program Counter Register

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计

与JVM不同,操作系统底层中指向的是下一条执行的指令地址

在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)

程序计数器的内存区域是唯一一个没有任何内存溢出OutOfMemoryError情况的区域。

栈内存,主管程序的运行,生命周期和线程同步;

线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题

栈:8大基本类型+对象引用+实例的方法

Native 本地方法栈

image-20220427134243600

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务

与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常

使用 native 修饰的指令都是调用本地方法接口

它的具体做法是Native Method Stack中登记native方法,在(Execution Engine)执行引擎执行的时候加载Native Libraies

线程共有区域

image-20220321152509202

方法区

Method Area方法区

方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;

静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关

static final,Class,常量池

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

HotSpot 和 堆

堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。

此内存区域的唯一目的就是存放对象实例,所有的对象实例以及数组都应当在堆上分配。

Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”(Garbage Collected Heap)。从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现“新生代”、“老年代”、“永久代”、“Eden空间”、“From Survivor空间”、“To Survivor空间”等名词。

Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。

Java堆既可以被实现成固定大小的,也可以是可扩展的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError (OOM) 异常。

栈、堆、方法区 的交互示例

数组的内存

image-20220327193425184

两个引用对象指向同一个数组

image-20220327194614132

一个对象的内存图

image-20220327202308049

两个对象使用同一个方法

image-20220327202710643

两个引用指向同一个对象的内存图

image-20220327203116428

4 JVM内存分配与回收策略

4.1 对象优先在Eden区分配

image-20220321152806036

Full GC 一般比 Minor GC 慢 10 倍以上

4.2 大对象直接进入老年代

image-20220321152944592

通过JVM参数设置 -XX:PretenureSizeThreshold 在 Serial和ParNew 收集器下有效,超过改参数大小直接进入老年代

设置原因:为了避免大对象分配内存时的复制

4.3 长期存活的对象将进入老年代

image-20220321153107983

在Eden区中坚持过 minor GC 进入survivor 区,age= 1;每坚持一次 minor GC , age += 1

默认 age = 15 进入老年代

image-20220321153127918

4.4 Minor GCEden区存活的对象 Survivor 区 放不下部分进入老年代

4.5 Eden与Survivor区默认8:1:1

image-20220321153643532

Eden 区满了后触发 Minor GC , (回收 99%),剩余的进入 survivor to 区(空的)

4.6 对象动态年龄判断

image-20220321154037785

4.7 老年代空间分配担保机制

image-20220321154324297

image-20220321154359690

配置担保参数: -XX:-HandlePromotionFailure

如果回收完还是没有足够空间存储新的对象,就会发生OOM

5 OutOfMemoryError (OOM)

遇到OOM 解决方法 -Xms1024m -Xmx1024m -XX:+PrintGCDetails (扩大初始内存)

  • 一般情况下分配的总内存是电脑内存的四分之一,初始化的内存是六十四分之一(如下)
import com.sun.management.OperatingSystemMXBean;

import java.lang.management.ManagementFactory;


public class Test {

    public static void main(String[] args) {

        long maxMemory = Runtime.getRuntime().maxMemory();

        long totalMemory = Runtime.getRuntime().totalMemory();

        System.out.println("虚拟机试图使用的最大内存 maxMemory:" + maxMemory + "字节 \t" + 
                           (maxMemory/(double)1024/1024) + "M");
        
        System.out.println("虚拟机初始化使用的最大内存 totalMemory:" + totalMemory + "字节 \t" + 
                           (totalMemory/(double)1024/1024) + "M");



        OperatingSystemMXBean osmb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
        long totalPhysicalMemorySize = osmb.getTotalPhysicalMemorySize();
        // long freePhysicalMemorySize = osmb.getFreePhysicalMemorySize();  // 电脑剩余内存

        System.out.println("电脑内存总大小:" + totalPhysicalMemorySize + "字节 \t" +
                (totalPhysicalMemorySize/(double)1024/1024) + "M");

        System.out.println("maxMemory/totalMemory : " + (double) maxMemory/totalMemory);
        System.out.println("totalPhysicalMemorySize/maxMemory : " + (double) totalPhysicalMemorySize/maxMemory);
        System.out.println("totalPhysicalMemorySize/totalMemory : " + (double) totalPhysicalMemorySize/totalMemory);
    }
}
# 正常情况下
虚拟机试图使用的最大内存 maxMemory:3797417984字节 	3621.5M
虚拟机初始化使用的最大内存 totalMemory:257425408字节 	245.5M
电脑内存总大小:17083187200字节 	16291.796875M
maxMemory/totalMemory : 14.75152749490835
totalPhysicalMemorySize/maxMemory : 4.498632300151871
totalPhysicalMemorySize/totalMemory : 66.36169806517312

# -Xms1024m -Xmx1024m -XX:+PrintGCDetails (VM options)
虚拟机试图使用的最大内存 maxMemory:1029177344字节 	981.5M
虚拟机初始化使用的最大内存 totalMemory:1029177344字节 	981.5M
电脑内存总大小:17083187200字节 	16291.796875M
maxMemory/totalMemory : 1.0
totalPhysicalMemorySize/maxMemory : 16.598876082526743
totalPhysicalMemorySize/totalMemory : 16.598876082526743
Heap
 PSYoungGen      total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afb8,0x00000000fab00000)
  from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
  to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
 ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
 Metaspace       used 3294K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 355K, capacity 388K, committed 512K, reserved 1048576K
  
# 305664k + 699392k =  981.5M  # 新生代 + 老年代 = 虚拟机初始最大内存
# Metaspace 元空间在逻辑上存在, 在物理上不存在

# -Xms4m -Xmx4m -XX:+PrintGCDetails

[GC (Allocation Failure) [PSYoungGen: 512K->504K(1024K)] 512K->520K(3584K), 0.0010041 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1014K->504K(1024K)] 1030K->636K(3584K), 0.0011249 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1016K->512K(1024K)] 1148K->788K(3584K), 0.0007922 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
虚拟机试图使用的最大内存 maxMemory:3670016字节 	3.5M
虚拟机初始化使用的最大内存 totalMemory:3670016字节 	3.5M
电脑内存总大小:17083187200字节 	16291.796875M
maxMemory/totalMemory : 1.0
totalPhysicalMemorySize/maxMemory : 4654.799107142857
totalPhysicalMemorySize/totalMemory : 4654.799107142857
Heap
 PSYoungGen      total 1024K, used 699K [0x00000000ffe80000, 0x0000000100000000, 0x0000000100000000)
  eden space 512K, 36% used [0x00000000ffe80000,0x00000000ffeaec30,0x00000000fff00000)
  from space 512K, 100% used [0x00000000fff00000,0x00000000fff80000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 2560K, used 276K [0x00000000ffc00000, 0x00000000ffe80000, 0x00000000ffe80000)
  object space 2560K, 10% used [0x00000000ffc00000,0x00000000ffc45060,0x00000000ffe80000)
 Metaspace       used 3295K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 355K, capacity 388K, committed 512K, reserved 1048576K
  

6 jprofiler 分析 OOM

分析 OOM 的软件 还有 MAT(基本不用了)

# VM options 
-Xms 	# 设置初始化内存分配大小 /164
-Xmx 	# 设置最大分配内存,默认1/4
 -XX:+PrintGCDetails 	# 打IGc垃圾回收信总
 -XX:+HeapDumpOnOutOfMemoryError  # OOM DUMP
# -Xms4m -Xmx4m -XX:+PrintGCDetails

# 在代码中加入:
        String str = "1234567890";
        while (true) {
            str += str;
        }

[GC (Allocation Failure) [PSYoungGen: 512K->488K(1024K)] 512K->528K(1536K), 0.0011578 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 998K->504K(1024K)] 1038K->616K(1536K), 0.0008813 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1016K->504K(1024K)] 1128K->744K(1536K), 0.0010449 secs] [Times: user=0.14 sys=0.00, real=0.00 secs] 
虚拟机试图使用的最大内存 maxMemory:1572864字节 	1.5M
虚拟机初始化使用的最大内存 totalMemory:1572864字节 	1.5M

[GC (Allocation Failure) [PSYoungGen: 937K->504K(1024K)] 1177K->896K(1536K), 0.0006825 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 504K->419K(1024K)] [ParOldGen: 392K->302K(512K)] 896K->721K(1536K), [Metaspace: 3291K->3291K(1056768K)], 0.0058845 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 832K->504K(1024K)] 1135K->982K(1536K), 0.0012532 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 504K->475K(1024K)] [ParOldGen: 478K->404K(512K)] 982K->879K(1536K), [Metaspace: 3297K->3297K(1056768K)], 0.0058477 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 817K->634K(1024K)] [ParOldGen: 404K->324K(512K)] 1222K->959K(1536K), [Metaspace: 3297K->3297K(1056768K)], 0.0048121 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 634K->622K(1024K)] [ParOldGen: 324K->319K(512K)] 959K->941K(1536K), [Metaspace: 3297K->3297K(1056768K)], 0.0046653 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
# GC GC GC GC FGC ...

Heap
 PSYoungGen      total 1024K, used 685K [0x00000000ffe80000, 0x0000000100000000, 0x0000000100000000)
  eden space 512K, 74% used [0x00000000ffe80000,0x00000000ffedfb10,0x00000000fff00000)
  from space 512K, 59% used [0x00000000fff00000,0x00000000fff4ba18,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 512K, used 319K [0x00000000ffe00000, 0x00000000ffe80000, 0x00000000ffe80000)
  object space 512K, 62% used [0x00000000ffe00000,0x00000000ffe4fc10,0x00000000ffe80000)
 Metaspace       used 3333K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 360K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space		# !!!!!! OOM
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at jvm.Test.main(Test.java:35)

内存设置

# VM options 
-Xms 	# 设置初始化内存分配大小 /164
-Xmx 	# 设置最大分配内存,默认1/4
-XX:+PrintGCDetails 	# 打IGc垃圾回收信总
-XX:+HeapDumpOnOutOfMemoryError  # OOM DUMP

-Xms2m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

String str = "1234567890";
int count = 0;
while (true) {
    str += str;
    count++;
}

使用 jprofiler 分析OOM原因

  • Current Object Set --> Biggest Objects --> 分析内存占用
  • ThreadDump --> main -- > 定位行数

image-20220427184228262

image-20220427184148512

7 JVM 配置参数

-Xms:初始堆大小。只要启动,就占用的堆大小。
-Xmx:最大堆大小。java.lang.OutOfMemoryError:Java heap这个错误可以通过配置-Xms和-Xmx参数来设置。
-Xmn:堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了

-Xss:栈大小分配。栈是每个线程私有的区域,通常只有几百K大小,决定了函数调用的深度,而局部变量、参数都分配到栈上。
# 当出现大量局部变量,递归时,会发生栈空间OOM(java.lang.StackOverflowError)之类的错误。

-XX:NewSize:设置新生代大小的绝对值。
-XX:NewRatio:设置年轻代和年老代的比值。比如设置为3,则新生代:老年代=1:3,新生代占总heap的1/4。

# java.lang.OutOfMemoryError:PermGenspace这个OOM错误 需要合理调大PermSize和MaxPermSize大小。
#-XX:MaxPermSize:设置持久代大小, JDK1.8以后,这个参数被替换成了 MaxMetaspaceSize

# 一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,
# 设置得比初始值要大,对于8G物理内存的机器来说,一般将这两个值都设置为256M	默认 20M 左右
-XX:MaxMetaspaceSize	# 设置元空间最大大小
-XX:MetaspaceSize	# 设置元空间大小

-XX:ThreadStackSize : 设置JVM栈内存

-XX:SurvivorRatio:年轻代中Eden区与两个Survivor区的比值。注意,Survivor区有form和to两个。比如设置为8时,那么eden:form:to=8:1:1。

-XX:HeapDumpOnOutOfMemoryError:发生OOM时转储堆到文件,这是一个非常好的诊断方法。
-XX:HeapDumpPath:导出堆的转储文件路径。

-XX:OnOutOfMemoryError:OOM时,执行一个脚本,比如发送邮件报警,重启程序。后面跟着一个脚本的路径。

8 Java虚拟机如何判断对象是否存活

8.1 引用计数法

当对象引用计数器为0时,即无其他对象引用,判定为死亡,可被回收,但是存在循环引用问题,导致对象一直存活。

image-20220321154629597

image-20220321154721275

8.2 可达性分析法

image-20220321154935886

对象的引用分类:

  • 强引用,程序中普遍存在的的对象引用
  • 软引用,SoftReference实现,内存溢出前回收
  • 弱引用,WeakReference实现,下一次垃圾回收
  • 虚引用,PhantomReference实现,形同虚设

对象的起死回生

image-20220321155150194

9 四种JVM垃圾回收算法

9.1 标记-清除算法

image-20220321155259306

9.2 标记-整理算法

image-20220321155555288

9.3 复制算法

image-20220321155749217

9.4 分代回收算法

image-20220321160005596

10 JVM垃圾收集器

垃圾收集器

如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。

以下七种收集器,如果相互之间存在连线,便可以搭配使用

image-20220321160100819

11 Serial 收集器 (单线程收集器)

新生代采用复制算法,老年代采用标记-整理算法
优点:简单而高效

image-20220321160411642

Serial Old 收集器 (单线程收集器)

用途:

image-20220321160625611

12 ParNew 收集器

Serial 收集器的多线程版本(回收策略、回收算法等完全一样)

新生代采用复制算法,老年代采用标记-整理算法

特点:

image-20220321161139812

13 Parallel Scavenge 收集器

Parallel Scavenge 收集器类似于 ParNew 收集器,是 Server 模式下的默认收集器

新生代采用复制算法,老年代采用标记-整理算法

特点:

image-20220321161655868

Parallel Old 收集器

使用多线程和“标记-整理”算法
在注重吞吐量已及CPU资源的场合,都可以优先考虑Parallel Scavenge收集器和Parallel Old 收集器

image-20220321162129779

14 CMS 收集器

CMS ( Conrrurent Mark Sweep )

以获取最短回收停顿时间为目标,HotSpot虚拟机第一款真正意义上的并发收集器

运作过程:

优点:

缺点:

image-20220321162245773

15 G1 收集器

G1(Garbage First)

基本特性:

  1. G1将堆划分为多个大小相等的独立区域(Region)
  2. 一般Region大小等于堆大小除以2048
  3. G1保留了年轻代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集合。
  4. 默认年轻代对堆内存的占比是5%
  5. Region的区域功能可能会动态变化

image-20220321162952583

G1 对大对象的处理:

G1 垃圾收集过程:

image-20220321163351857

16 JMM

由于主存与 CPU 处理器的运算能力之间有数量级的差距,所以在传统计算机内存架构中会引入高速缓存来作为主存和处理器之间的缓冲,CPU 将常用的数据放在高速缓存中,运算结束后 CPU 再讲运算结果同步到主存中。

使用高速缓存解决了 CPU 和主存速率不匹配的问题,但同时又引入另外一个新问题:缓存一致性问题

在多CPU的系统中(或者单CPU多核的系统),每个CPU内核都有自己的高速缓存,它们共享同一主内存(Main Memory)。当多个CPU的运算任务都涉及同一块主内存区域时,CPU 会将数据读取到缓存中进行运算,这可能会导致各自的缓存数据不一致。因此需要每个 CPU 访问缓存时遵循一定的协议,在读写数据时根据协议进行操作,共同来维护缓存的一致性。这类协议有 MSI、MESI、MOSI、和 Dragon Protocol 等。

为了使处理器内部的运算单元能够最大化被充分利用,处理器会对输入代码进行乱序执行处理,这就是处理器优化。

除了处理器会对代码进行优化处理,很多现代编程语言的编译器也会做类似的优化,比如像 Java 的即时编译器(JIT)会做指令重排序。

处理器优化其实也是重排序的一种类型,这里总结一下,重排序可以分为三种类型:

JMM的三个特征:

如果从更深层次看这三个问题,其实就是『缓存一致性』、『处理器优化』、『指令重排序』造成的。

image-20220427211716566

JMM规则:

为了更好的控制主内存和本地内存的交互,Java 内存模型定义了八种操作来实现:

关键词synchronized与volatile总结

synchronized的特点

一个线程执行互斥代码过程如下:

所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性

volatile是第二种Java多线程同步的手段,根据JLS的说法,一个变量可以被volatile修饰,在这种情况下内存模型确保所有线程可以看到一致的变量值

class Test {  
    static volatile int i = 0, j = 0;  
    static void one() {  
        i++;  
        j++;  
    }  
    static void two() {  
        System.out.println("i=" + i + " j=" + j);  
    }  
}  

加上volatile可以将共享变量i和j的改变直接响应到主内存中,这样保证了i和j的值可以保持一致,然而我们不能保证执行two方法的线程是在i和j执行到什么程度获取到的,所以volatile可以保证内存可见性,不能保证并发有序性(不具有原子性)

如果没有volatile,则代码执行过程如下:

17 一些关于 JVM 的问题

请你谈谈你对JVM的理解?

JVM的内存模型和分区详细到每个区放什么?

堆里面的分区有哪些?Eden,form,to,老年区,说说他们的特点

引用计数器,怎么用的?

轻GC和重GC分别在什么时候发生?

什么是OOM,什么是栈溢出StackOverFlowError?怎么分析?

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

-Xms:初始堆大小。只要启动,就占用的堆大小。
-Xmx:最大堆大小。java.lang.OutOfMemoryError:Java heap这个错误可以通过配置-Xms和-Xmx参数来设置。
-Xmn:堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了

-Xss:栈大小分配。栈是每个线程私有的区域,通常只有几百K大小,决定了函数调用的深度,而局部变量、参数都分配到栈上。

-XX:NewSize:设置新生代大小的绝对值。
-XX:NewRatio:设置年轻代和年老代的比值。比如设置为3,则新生代:老年代=1:3,新生代占总heap的1/4。

-XX:SurvivorRatio:年轻代中Eden区与两个Survivor区的比值。注意,Survivor区有form和to两个。比如设置为8时,那么eden:form:to=8:1:1。

# java.lang.OutOfMemoryError:PermGenspace这个OOM错误 需要合理调大PermSize和MaxPermSize大小。
#-XX:MaxPermSize:设置持久代大小, JDK1.8以后,这个参数被替换成了 MaxMetaspaceSize

# 一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,
# 设置得比初始值要大,对于8G物理内存的机器来说,一般将这两个值都设置为256M	默认 20M 左右
-XX:MaxMetaspaceSize	# 设置元空间最大大小
-XX:MetaspaceSize	# 设置元空间大小

-XX:ThreadStackSize : 设置JVM栈内存



-XX:HeapDumpOnOutOfMemoryError:发生OOM时转储堆到文件,这是一个非常好的诊断方法。
-XX:HeapDumpPath:导出堆的转储文件路径。

-XX:OnOutOfMemoryError:OOM时,执行一个脚本,比如发送邮件报警,重启程序。后面跟着一个脚本的路径。

内存快照如何抓取,怎么分析Dump文件?知道吗?

抓取内存快照

-XX:HeapDumpOnOutOfMemoryError:发生OOM时转储堆到文件,这是一个非常好的诊断方法。
-XX:HeapDumpPath:导出堆的转储文件路径。

使用 jprofiler 分析OOM原因

  • Current Object Set --> Biggest Objects --> 分析内存占用
  • ThreadDump --> main -- > 定位行数

谈谈JVM中,类加载器你的认识?

种类

双亲委派机制

沙箱安全机制

java8虚拟机和之前的变化更新?

jdk1.8 之前 jdk1.8
image-20220427215617892 image-20220427215646067

堆和方法区连在了一起,但这并不能说堆和方法区是一起的,它们在逻辑上依旧是分开的。但在物理上来说,它们又是连续的一块内存。也就是说,方法区和前面讲到的Eden和老年代是连续的

image-20220427212939695

image-20220427214418582

在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。

方法区和永久代的关系很像Java中接口和类的关系,永久代是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。

# JDK1.8之前调节方法区大小:
-XX:PermSize=N //方法区(永久代)初始大小
-XX:MaxPermSize=N //方法区(永久代)最大大小,超出这个值将会抛出OutOfMemoryError 

# JDK1.8开始方法区(HotSpot的永久代)被彻底删除了,取而代之的是元空间,元空间直接使用的是内存。参数设置:
-XX:MetaspaceSize=N //设置Metaspace的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置Metaspace的最大大小

永久代和元空间内存使用上的差异:

永久代为什么被替换了

表面上看是为了避免OOM异常。因为通常使用PermSize和MaxPermSize设置永久代的大小就决定了永久代的上限,但是不是总能知道应该设置为多大合适, 如果使用默认值很容易遇到OOM错误

当使用元空间时,可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制

更深层的原因还是要合并HotSpot和JRockit的代码,JRockit从来没有所谓的永久代,也不需要开发运维人员设置永久代的大小,但是运行良好。同时也不用担心运行性能问题了,在覆盖到的测试中, 程序启动和运行速度降低不超过1%,但是这点性能损失换来了更大的安全保障。

标签:收集器,虚拟机,XX,线程,内存,JVM,0.00
来源: https://www.cnblogs.com/kitelee/p/16220138.html