欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

浅谈JVM内存+OOM、SOF

程序员文章站 2022-03-13 10:46:29
...

运行时数据区域

线程私有

程序计数器

较小的内存空间,可看成当前线程执行的字节码的行号指示器。

Java虚拟机栈

Java方法执行的内存模型(非native方法)。每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表存基本数据类型、对象引用、returnAddress类型。

本地方法栈

虚拟机栈为虚拟机执行Java方法(字节码),本地方法栈则为虚拟机使用到的Native方法(HotSpot把本地方法栈和虚拟机栈合并了)。

线程共享

Java堆

存对象实例、和数组对象,垃圾收集管理器的主要区域。

方法区(永久代)

存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(类的版本、字段、方法、接口)。
元空间并不在虚拟机中,而是使用本地内存。

杂项内存

运行时常量池

方法区一部分,存放各种编译及生成的各种自变量和符号引用,具备动态性。

浅谈JVM内存+OOM、SOF

垃圾回收

判定对象为垃圾对象

引用计数法 可达性分析法
在对象中添加一个引用计数器,当有地方引用这个对象时,引用计数器值+1,当引用失效时,计数器值-1。(一般不采用)。 从GCRoots的对象(虚拟机栈、方法区的类属性引用的对象、方法区中常量引用的对象、本地方法栈中引用的对象)的引用连是否引用来判断是否为垃圾。

回收策略

算法 简介
标记–清除      标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。未被标记的对象就是未被引用的垃圾对象。清除阶段,清除所有未被标记的对象。
标记–压缩   标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。标记-压缩算法从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。
标记-复制 将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

垃圾回收器

收集器 简介
Serial(串行) 单线程垃圾收集器,最基本,发展历史最久(单线程中效率高,桌面应用多)。
Parallel(并行) 复制算法(新生代收集器)多线程,控制吞吐量(CPU用于用户运行代码的时间与CPU消耗的总时间比值)。
Parnew(并行) 复制算法(新生代收集器)多线程,控制收集时间。
Cms(Concurrent Mark Sweep 初始标记(Stop the world)、并发标记、从新标记(Stop the world)、并发清除。
G1 初始标记、并发标记、最终标记、筛选回收。

Cms运行图

浅谈JVM内存+OOM、SOF

CMS收集器是基于标记清除算法的一种并发的,低停顿的收集器,值得注意的一点是,CMS只是低停顿而不是没有停顿。

CMS缺点:

  1. 他没有办法处理浮动垃圾(就是在初始标记之后产生的垃圾)
  2. 他会占用大量的CPU资源
  3. 他会产生碎片空间(当发生Full GC时会使用Serial Old回收器来处理碎片空间)
  4. 他在回收垃圾时,由于是并行的收集,所以需要的空间比较大

G1收集器:

  1. G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。因此,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
  2. G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
  3. G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
  4. G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。
  5. 祥述

内存分配: new小对象进新生代,大对象直接进入老年代,长期存活的对象将进入老年代。

Class文件

Class文件是一组以8位字节为基础单元的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,无空隙无分隔符,整个Class
文件中储存的内容几乎全部是程序运行的必要数据。
当遇到8位字节以上的空间数据项,会按照高位在前的方式分割成若干个8位字节进行存储。
Class文件有两种数据类型为,无符号数、表。

类装载

阶段 描述
加载 取得类的二进制流转为方法区数据结构,在内存中生成一个代表这个类的Class对象作为这个类的各种数据访问入口。
链接 验证:确保Class文件的字节流的信息符合当前虚拟机的要求,且不危害虚拟机的安全。(文件格式、元数据、字节码、符号引用)验证准备:正式为类变量分配内存并设置变量的初始值。这些变量使用的内存都将在方法区进行分配。(int 0、Boolean false、Float 0.0)解析:类或接口解析、字段解析、类方法解析、接口方法解析
初始化                      1:执行类构造器<clinit>() 、static变量。赋值语句static{}语句。2:子类的<clinit>()调用前保证父类的<clinit>()被调用。<clinit>()是线程安全的

<clinit>() :方法是由编译器自动收集类中的所有变量赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是由语句源文件中出现的顺序决定的,静态语句块中只能访问定义在静态语句块之前的变量,定义在它之后的变量。子类的<clinit>()在执行之前,虚拟机保证父类的执行完毕。接口中也有变量要赋值,也会生成<clinit>(),但不需要先执行父类的<clinit>()方法,只有父类接口中定义的变量使用时才会初始化。

线程栈相关的内存异常:

内存溢出:  申请的内存超出了JVM能提供的内存大小。
内存泄露:  编程错误导致申请内存后,不释放,且叠加。

OOM(OutOfMemoryError)

原因分析:流量/数据量峰值: 用户数量或数据量超过了设计之初预期的阈值。

java.lang.OutOfMemoryError: Java heap space(堆内存溢出)

一般由于内存泄露或堆的大小设置不当引起。
通过内存监控软件查找程序中的泄露代码,堆大小可以通过虚拟机参数-Xms,-Xmx修改。
代码

int[] i = new int[999999999];

java.lang.OutOfMemoryError: PermGen space(永久代溢出)

即方法区溢出,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,会产生大量的Class信息存储于方法区。
通过更改方法区的大小来解决,修改-XX:PermSize=64m -XX:MaxPermSize=256m。

代码

<dependency>
  <groupId>org.javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.25.0-GA</version>
</dependency>
public static void main(String[] args) throws Exception {
	for (int i = 0; i < 100_000_000; i++) {
	  generate("eu.plumbr.demo.Generated" + i);
	}
}

public static Class generate(String name) throws Exception {
	ClassPool pool = ClassPool.getDefault();
	return pool.makeClass(name).toClass();
}

java.lang.OutOfMemoryError: Metaspace(元空间用尽)

JAVA8引入了Metaspace区域,元空间并不在虚拟机中,使用本地内存。
通过设置-XX:MaxMetaspaceSize=512m 空间分配。

java.lang.OutOfMemoryError: GC overhead limit exceeded

程序耗尽了所有的可用内存,GC在连续多次都只回收了不到2%的极端情况下才会抛出。

代码

public static void main(String args[]) throws Exception {
	Map map = System.getProperties();
	Random r = new Random();
	while (true) {
		map.put(r.nextInt(),"value");
	}
}

SOF(*)栈

java.lang.*Error:

栈溢出的原因:
1、递归调用太深
2、大量循环或死循环
可以通过虚拟机参数-Xss来设置栈的大小。

代码

int count = 1;
public void stackException(){
	count++;
	this.stackException();
}

 

相关标签: Java基础