初识JVM虚拟机

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1 内存区域

1.1 程序计数器
  1. 一块较小的内存空间
  2. 可看作当前线程所执行的字节码的行号指示器
  3. 通过改变这个计数器的值来选取下一次执行的字节码指令 。用于指示分支、循环、跳转、异常处理、线程恢复等等。
  4. 线程私有,生命周期与线程相同,每一个线程都有程序计数器
  5. 如果执行的是java方法,则计数器记录正在执行的虚拟机字节码
  6. 如果执行的是native方法,则计数器值为空(Undefined)
  7. 唯一不会出现OutOfMemoryError
  8. 物理结构为:寄存器

线程私有吗?为什么?

java虚拟机采用线程轮流切换、分配处理器执行时间的方式实现。为了切换线程后能恢复到正确的执行位置,每个线程都有自己的程序计数器,独立存储所以线程私有。

1.2 Java虚拟机栈
  1. 线程私有
  2. 描述的是Java方法执行的线程内存模型
  3. 一个栈帧存储了局部变量表、操作数栈、动态连 接、方法出口等
  4. 方法调用就是栈帧入栈到出栈的整个过程
  5. 局部变量表存储:1.基本数据类型2.对象引用3.returnAddress(指向了一条字节码指令的地址)。
  6. 局部变量表被定义为一个数字数组。
  7. 数据类型在局部变量表中以局部变量槽(Slot)表示,64位长度的long和double占2个变量槽
  8. 线程请求栈深度大于虚拟机所允许的深度出现StackOverflowError
  9. 栈扩展无法申请到足够内存抛出OutOfMemoryError
  10. 活动栈帧:当前正在执行的方法所对应的栈
  11. -Xss:1024K指定栈(线程内存分配大小)的大小为1兆

-Xss 为jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5+中是1M

  • 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

注意:
第7点byteshortcharboolean存储前转化为int,boolean:0表示false,非0表示true

第8点HotSpot不会由此产生OOM,因为HotSpot栈的容量是不能动态拓展的。当线程申请栈空间失败了会OOM。

在这里插入图片描述

方法内的局部变量是否线程安全?

不一定,若局部变量为引用对象,并逃离了作用方法范围则线程不安全

在这里插入图片描述

1.2.1 局部变量表(Local Variable Table)
  • 一组变量值存储空间
  • 存放方法参数和方法内定义的局部变量
  • 8种基本类型(boolean用0和1表示)、对象引用、returnAddress
  • 64位长度long和double用占2个局部变量空间(slot),其余占用1个
1.2.2 操作栈
  • 先入后出的栈
  • 随着方法和字节码指令执行,从局部变量表或者对象加载数据到操作数栈,随着计算将栈元素的局部变量表或返回值给方法者调用。
1.2.3 动态链接
  • 每个栈帧都有,指向运行时常量池中该栈帧所属方法的符号引用
  • 符号引用转换成直接引用,支持方法调用过程中动态链接

为什么还要再做一次符号引用转直接引用?(类加载中链接的第三步:解析已经作过了)

java存在多态,不清楚是调用父类的引用还是子类的,所以需要动态链接

1.2.4 方法返回地址
  • 方法结束后,返回调用该方法的PC寄存器的值(以供调用方继续运行)
  • 2种返回
    • 正常返回:返回被调用方法的PC寄存器值
    • 异常返回:根据报错,从异常表中返回值
1.3 本地方法栈
  • 服务于native方法
  • 可能抛出异常:与Java虚拟机栈一样
1.4Java堆
  1. 线程共享
  2. 存放实例对象和数组
  3. 将堆细分是为了更好的回收内存或更快的分配内存
  4. java堆空间可通过-Xmx-Xms设定(两个参数设置成相同值避免堆进行拓展)
  5. Java堆中没有完成实例分配或者无法拓展会抛出OutOfMemoryError
  6. 新生代中的tlab区域是线程私有
  7. 虚拟机启动的时候创建堆
  8. 物理上不是连续的,逻辑上是连续的

注意:对于2,由于逃逸分析技术、栈上分配、标量替换导致实例对象有可能不分配在堆上。

在这里插入图片描述

1.4.1堆大小指令
  • -Xms:初始堆大小(最小堆大小)
  • -Xmx:最大堆内存
  • -Xmn:年轻代大小
  • -Xss:每个线程堆大小,值太小容易栈溢出(StackOverFlowError)

为什么-Xms-Xmx要设置成一样的?

默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。
因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。会发生内存抖动影响程序运行时的稳定性

1.4.1 配置新生代和老年代堆结构占比

在这里插入图片描述

  • -XX:NewRatio:老年代与新生代占比

  • 默认-XX:NewRato=2,代表新生代:老年代=1:2,新生代占整个堆的1/3

  • -XX:SurvivorRatio:eden与survivor占比

  • 默认 -XX:SurvivorRatio=8,代表eden:s1:s2=8:1:1

年轻代垃圾回收为什么默认是15就进入老年代

对象年龄用 4位来表示:0000-1111 1111=15

survivor阈值变化

survivor区域放不下的时候触发:Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值

所以老年代中还可能存在年龄不到15的对象

这计算出来的新阈值是在下次GC的时候使用,本次GC中放不下的对象直接进老年代

分配担保机制

大对象放不进eden区 直接放老年代。

有些JVM可以设置Enen区大对象的阈值

1.5方法区
  1. 存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码
    • 类信息:Class类,如类名、访问修饰符、运行时常量池、字段描述、方法描述
  2. 垃圾收集行为在此区域很少发生;
    • 不过也不能不清理,对于经常动态生成大量 Class 的应用,如 Spring 等,需要特别注意类的回收状况。
  3. 所有虚拟机线程共享的区域

在这里插入图片描述
在这里插入图片描述

1.5.1方法区结构

在这里插入图片描述

在这里插入图片描述

类型信息

  • 方法区中保存每个加载类型信息
    • 类的名称(全名=包名.类名)
    • 直接父类的完整有效名(interface和java.lang.Object,无父类)
    • 类型的修饰符(public,abstract,final的某个子集)
    • 类型直接接口的有序列表

域信息

  • 类属性,成员变量
  • 保存类所有的成员变量相关信息及声明顺序
  • 域的相关信息包括:域名称、域类型、域修饰符(pυblic、private、protected、static、final、volatile、transient的
    某个子集)

方法信息

  1. 方法名称方法的返回类型(或void)
  2. 方法参数的数量和类型(按顺序)
  3. 方法的修饰符public、private、protected、static、final、synchronized、native,、abstract的一个子集
  4. 方法的字节码bytecodes、操作数栈、局部变量表及大小( abstract和native方法除外)
  5. 异常表( abstract和 native方法除外)。每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏
    移地址、被捕获的异常类的常量池索引
1.5.2常量池
  • 静态常量池(Class文件中的常量池):存储了符号引用以及字面量
  • 运行时常量池:存储运行期间所需要的常量。还存储java基本类型封装类的常量池。(Integer、Boolean······、不存浮点类型)
  • 字符串常量池:包含在运行时常量池之中。

jdk1.8:
在这里插入图片描述

String s = new String(“a”);创建了几个对象?

1个或者2个

2个:a存放进字符串常量池,s(a的引用)指向a并创建在堆中

1个:a在字符串常量池中已经有了,直接将s放入堆中

为什么需要字符串常量池?

编译期间将相同的字符串过滤只剩一个,减少占用空间

1.5.2方法区设置大小

jdk1.7

  • -xx:Permsize:永久代初始化大小,默认20.75
  • -XX:MaxPermsize:永久代最大分配空间。32位机器默认是64M,64位机器模式是82M
jps #是java提供的一个显示当前所有java进程pid的命令 
jinfo -flag PermSize 进程号 #查看进程的PermSize初始化空间大小 
jinfo -flag MaxPermSize 进程号 #查看PermSize最大空间

jdk8以后

元空间设置

  • -XX:MetaspaceSize:初始化大小
  • -XX:MaxMetaspaceSize:最大分配空间
  • 一旦空间高于-XX:MetaspaceSize分配的大小就会Full GC,然后-XX:MetaspaceSize会被重置。-XX:MetaspaceSize设置太低会频繁Full GC导致STW。
jps #查看进程号 
jinfo -flag MetaspaceSize进程号#查看Metaspace最大分配内存空间 
jinfo -flag MaxMetaspaceSize 进程号 #查看Metaspace最大空间
1.6元空间
  • JDK1.7之前把方法区当成永久代来进行垃圾回收。1.8之后移除永久代,将方法区移至元空间。
  • 方法区只是一种规范,元空间是他的一种实现

在这里插入图片描述

元空间和永久代的差异

  • 存储位置不同:

    • 永久代在物理上是堆的一部分,新生代、老年代的地址是连续的
    • 元空间属于本地内存。
  • 存储内容不同:

    • 永久代存储类的元数据信息、静态变量、常量池
    • 类元信息存储在元空间,静态变量和常量池存在堆中(永久代的数据被元空间和堆瓜分)
  • 类加载器内存卸载方式不同(移除类加载器)

    • 永久代线性分配内存块(代理、反射分配的内存块会小一点)
    • 永久代卸载的时候会一个个去卸载
    • 元空间直接将整个类加载器和整个空间干掉

在这里插入图片描述

为什么废除永久代?

  1. 永久代大小不容易确定,需要多方面考虑(类总数、常量池大小、方法数量),-XX:MaxPermSize 指定太小很容易造成永久
    代内存溢出。
  2. 为了融合HotSpot 和JRockit,JRockit没有永久代
  3. 永久代会为GC带来不必要的复杂度,影响回收效率

废除永久代 有什么好处?

  • 直接分配在本地内存中,不容易造成内存溢出
  • 将类元数据剥离到元空间,提升元数据独立性
  • 元数据移到元空间,有利于对元空间的GC管理,效率上升

Metaspace参数

  • -XX:MetaspaceSize:初始空间大小,达到该值就进行GC。如
    果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当
    该值。
  • -XX:MaxMetaspaceSize:最大空间,默认无设置(系统内存可用空间),若没设置该值,元空间占用内存过大,导致swap内存被耗尽;最终导致进程直接被系统直接kill掉。如果设置了该参数,当Metaspace剩余空间不足,会抛出:java.lang.OutOfMemoryError: Metaspace space
  • -XX:MinMetaspaceFreeRatio:在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的
    垃圾收集
1.7运行时常量池
  1. 方法区的一部分
  2. 存放编译期生成的各种字面量与符号引用
  3. 存储class文件描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中。
  4. 运行时常量池相对于Class文件常量池更具动态性,如String类中的intern()可以在程序运行时新常量加入常量池中
  5. 当类被加载,它的常量池信息将放入运行时常量池,并把符号转换为真是地址
  6. 受方法区限制,若常量池不能申请到内存时会抛出OutOfMemoryError异常
java代码:

public static void main(String[] args) {
        System.out.println("hello world");
}

使用javap -v 类.class 可查看

Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // hello world
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // cn/itcast/jvm/t5/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcn/itcast/jvm/t5/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               hello world
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               cn/itcast/jvm/t5/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public cn.itcast.jvm.t5.HelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/itcast/jvm/t5/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}

常量池和运行时常量池差别

存储位置不同

  • 运行时常量池存在方法区中

运行时常量池是常量池在运行时的表现形式。

1.8直接内存
  1. 能够直接提高性能,比如NIO可以使native函数库直接分配堆外内存,然后通过存储在java堆里面的DirectByteBuffer对象作为引用
  2. 当各个内存区域总和大于物理内存限制导致OutOfMemoryError异常

在这里插入图片描述

在这里插入图片描述

直接内存的好处:

  1. 改善GC效率,减少停顿(少管理了方法区)。堆外内存有OS负责管理和回收。
  2. 实现了NIO,减少内存native和JVM拷贝过程,降低内存使用
  3. 使用直接内存,突破JVM堆内存大小限制

2 Java对象的创建

2.1对象的创建

image

2.1.1类加载器检查

虚拟机遇到一条new指令,首先检查这个指令参数是否能在常量池中定位到这个类的符号引用,并检查这个符号引用代表的类是否已被加载过、解析和初始化。如果没有必须先执行相应的类加载过程。

符号引用和直接引用的区别?

  1. 符号引用是在方法区中的,直接引用是在虚拟机栈中。
  2. 符号引用用一组符号来描述所引用的目标,只为保证能够无歧义的定位到目标即可
  3. 在编译期符号引用所表示的类实际内存地址可能不存在所以引入符号引用为了准确定位到所表示的类信息
  4. 类加载的解析阶段是将符号引用替换为直接引用的过程
2.1.2.分配内存

类加载检查后,对象所需的内存大小即可确定,在Java堆中开辟并分配一块空间。分配内存有2种方式:"指针碰撞"和“空闲列表”,选择哪种分配方式由java堆是否规整决定,Java堆是否规整由采用垃圾收集器是否带有压缩整理功能决定

image

内存分配并发问题(如何保证内存分配)

创建对象是频繁的事情,作为虚拟机必须保证线程安全

  • CAS+失败重试:CAS是乐观锁,虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
  • TLAB:预先为每个线程在堆的Eden中分配空间,分配内存时当对象大于TLAB的剩余内存或TLAB内存用尽则进行CAS进行分配
  • 虚拟机是否使用TLAB进行预分配可通过-XX:+/-UseTLAB
2.1.3.初始化零值

内存分配完毕之后将对这些内存空间进行初始化零值(不包括对象头)。

若使用了TLAB,可提前至TLAB分配时进行。

初始化零值保证了Java代码在不赋值的情况下就可直接使用,程序能访问到这些字段的数据类型所对的零值

2.1.4.设置对象头

初始化零值完成之后,虚拟机对对象进行必要的设置,如:这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

2.1.5.执行init方法

设置对象头结束之后在虚拟机角度来看,一个新的对象已经产生了,但从Java程序来看,对象创建才刚刚开始,<init>方法还没有执行,所有字段都还为零。当执行new指令后会调用init方法,把对象按照程序员的意愿初始化。

2.2 对象的内存布局

在这里插入图片描述

在HotSpot中,对象在内存中的布局可以分为3块区域:对象头、实例数据、对齐填充

对象头包括:

  1. 标记字段(Mark Word运行时数据信息):存储对象自身运行时数据(哈希码、GC分代年龄、锁状态标志)

MarkWord被设定为动态定义的数据结构,以便在最小的空间存储最多的数据。
如存储锁标志位 01表示未锁定或偏向锁 00表示轻量级锁 10表示重量级锁

如果对象是java数组则需要在对象头中记录数组长度,虚拟机只能知道一个对象的大小,数组长度不确定则不能计算出数组大小。

MarkWord中哈希码是大端储存

  • 大端存储:数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的高地址
  • 小端存储:数据的高字节保存在内存的高地址中,而数据的低字节保存在内在的低地址

例子:

大端:

12 18 02 5c(00010010 00011000 00000010 01011100)

小端
5c 02 18 12(01011100 00000010 00011000 00010010)

小端存储优点:便于数据类型转换。假设java long能转int 直接干掉高位就可转换。

大端存储优点:最末位16进制字节码就能拿到符号位,便于判断符号,HashCode对每个对象都是一个定值,不需要类型转换。

变量都是小端储存

  1. 类型指针(Class Point):对象指向它的类元数据的指针(记录方法区该类元数据的内存地址),虚拟机根据这个指针判断该对象是哪个类的实例

并不是所有虚拟机实现都必须在对象数据上保留类型指针(访问对象有2种方法)。

Class Pointer记录的是引用地址(时间换空间),减少数据冗余

在这里插入图片描述

为什么类型指针只有4字节(在64位机器上)?

  • -XX:-UseCompressedOops:不开启指针压缩
  • -XX:+UseCompressedOops:开启指针压缩(默认)
  • 堆<4G(2^32=4G,不需要开启指针压缩。JVM自动去除高32位地址)
  • 堆>32G(2^64=32G),强制使用64字节,所以jvm内存不要超过32G,因为指针压缩技术会失效

8字节保存信息的缺点

  1. 增加GC开销,减少空间。64位占用更多堆内存,留给其它数据的空间减小,增大GC频率
  2. 降低CPU缓存命中率:64位对象引用增大,可存储oop(对象指针)容量降低,效率也变慢。

指针压缩实现方式

每隔8字节保存一个引用

在实现上,堆中的引用其实还是按照0x0、0x1、0x2…进行存储。只不过当引用被存入64位的寄存器时,JVM将其左移3位(相当于末尾添加3个0),例如0x0、0x1、0x2…分别被转换为0x0、0x8、0x10。而当从寄存器读出时,JVM又可以右移3位,丢弃末尾的0。(oop在堆中是32位,在寄存器中是35位,2的35次方=32G。也就是说,使用32位,来达到35位oop所能引用的堆内存空间


实例数据:对象真正存储的有效信息

字段存储顺序受虚拟机分配策略参数-XX:FieldsAllocationStyle参数和字段在Java源码中定义的顺序影响。

默认顺序:longs/doubles、ints、shorts/chars、bytes/booleans、oops。

父类定义的变量会出现在子类之前若采用+XX:CompactFields参数为true那么子类之中较窄的变量允许插入父类的空隙中。


对齐填充:仅仅起占位作用。Hotspot自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。若对象头恰好是8字节的倍数,就不需要对齐填充。

为什么要对齐填充?

64位CPU每次处理8字节数据,若不对齐则可能出现数据跨内存区域存储。影响查询效率

long类型需要分2次访问:0X00-0X07(读取1位long数据)、0X08-0X0F(读取后7位long数据)
在这里插入图片描述

long类型1次访问即可:直接读0X08-0X0F
在这里插入图片描述

对齐填充策略

在这里插入图片描述

  • 0:基本类型->对齐填充->引用类型
  • 1: 引用类型->基本类型—>填充字段(默认)
  • 2: 父类采用0,子类采用1

注意:父类在对齐填充产生的空隙,子类并不会将数据插入到父类空隙中。

2.3 对象的访问定位

对象访问:Java程序通过栈上的reference数据来操作堆上的具体对象。
对象访问定位有2种方式:1. 使用句柄 2.直接指针

句柄:Java堆会划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,句柄中包括了对象实例数据与类型数据各自的具体地址信息

栈中的reference存储句柄地址

句柄池存储对象实例数据对象类型数据地址
在这里插入图片描述

直接指针:reference存储对象地址,Java堆中的对象实例数据存放对象类型地址,HotSpot采用这种方式,在MarkWord中的Class Ponit 中记录指向类型数据的指针

栈中的reference存储对象实例数据

堆中的对象实例数据存储对象实例数据对象类型数据地址

在这里插入图片描述

各自优势:

句柄访问:是存储的是稳定的句柄地址,对象被移动时只改变句柄的实例数据指针,而不需要修改reference指向句柄池的指针

直接指针:速度快,减少了一次指针定位的开销,快一倍
(空间换时间,HotSpot大部分都是空间换时间)

2.4内存溢出

2.4.1java 堆溢出

前提:设置-Xms:20m(堆的最小值) -Xmx:20m(堆的最大值) -XX:+HeapDumpOnOutOfMemoryError(出现内存溢出异常Dump出当前内存转储快照)

public class HeapOOM {

    static class OOMObject{}

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true){
            list.add(new OOMObject());
        }
    }
}

//结果
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:265)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
	at java.util.ArrayList.add(ArrayList.java:462)
	at OOMTest.HeapOOM.main(HeapOOM.java:13)

可能导致堆OOM的情况

  • 内存加载的数据过多,一次从数据库中取出过多数据
  • 集合对象引用过多,使用完后且未清空
  • 代码死循环
  • 堆内存分配不合理

处理内存区域异常步骤

  1. 通过内存映像分析工具对Dump出的堆转储快照进行分析
  2. 分析是内存泄露还是内存溢出
    • 若为内存泄露
      • 查看泄漏对象到GC Root的引用链,找到泄漏对象是通过怎样的引用路径、与哪些GCRoot相关联,才导致垃圾收集器无法回收
    • 若为内存溢出
      • 检查虚拟机堆参数(-Xmx与-Xms)与机器内存对比是否可向上调整
      • 再从代码检查是否存在对象生命周期过长、持有状态实践过长、存储结构设计不合理等情况,尽量减少程序运行时内存消耗

2.4.2 虚拟机栈和本地方法栈溢出

-Xss:设置每个线程的内存大小

-Xoss:设置本地方法栈大小

  1. 栈针太大或者本地虚拟机栈容量太小会产生StackOverflowError
  2. 多线程情况下会产生OutOfMemoryError
    • 减小堆内存
    • 减小栈容量已换取更多的线程-xss

为什么多线程栈会产生OOM

  1. 栈是线程私有的,多线程情况每个线程都需要耗费栈内空间,若每个线程分配到的栈内存越大,可以建立的线程数据就越少,线程一多就会产生OOM。

多线程情况下发生OOM有2种结局方案

  • 减少最大堆(栈内存 ≈ JVM占用内存-最大堆容量-最大方法区容量-程序计数器<占用内存很小>-直接内存-虚拟机进程耗费内存)
  • 减小每次分配栈的容量

2.4.3 方法区和运行时常量池溢出

常量池内存溢出

  • jdk1.6及其之前 常量池是在永生代中可调用指令-XX:PermSize-XX:MaxPermSize限制永生代的大小,可间接控制常量池大小。

  • jdk1.7、1.8,常量池在堆里面,设置-Xmx

方法区内存溢出

  • jdk1.6方法区在永久代
  • jdk1.8方法区实现是元空间

设置永久代大小:

  • -XX:PermSize-XX:MaxPermSize

设置元空间大小指令:

  • -XX:MaxMetaspaceSize:设置元空间最大值,默认-1,只受本地内存大小限制
  • -XX:MetaspaceSize:设置元空间初始大小,以字节为单位。到达该值触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:
    • 释放大量空间,降低该值
    • 释放极少空间,升高该值,但要小于-XX:MaxMetaspaceSize设定的值

控制元空间垃圾收集频率:

  • -XX:MinMetaspaceFreeRatio:垃圾收集后控制最小元空间剩余容量百分比,减少元空间不足导致的垃圾收集频率
  • -XX:MaxMetaspaceFreeRatio:垃圾收集后控制最大元空间剩余容量百分比

2.4.4 本机直接内存溢出

  • -XX:MaxDirectMemorySize:控制直接内存大小。默认与-Xmx一致

本机直接内存溢出特征

Dump文件很小,没有明显异常信息,程序直接或间接使用了DirectMemory(典型的间接使用了NIO).

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值