聊一聊JVM (3)

运行时的常量池

创建对象内存分析 public class Person{ int age; String name = "xing"; public Person(int age, String name){ this.age = age; this.name = name; } public static void main(String[] agrs){ Person s1 = new Person(18,"Tom"); } } /* 创建一个对象时,方法区中会生成对应类的抽象模版;还有对应的常量池、静态变量、类信息、常量。 我们通过类模版去new对象的时候,堆中存放实例对象,栈中存放对象的引用,每个对象对应一个地址指向堆中相同地址的实例对象。 */ 栈

​ 主管程序的运行,生命周期和线程同步。线程结束,栈内存就释放了,不存在垃圾回收。栈中存放8大基本类型,对象引用,实例的方法。

栈运行的原理

​ 栈表示java方法执行的内存模型,每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),每个方法被调用的完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。程序正在执行的方法一定在栈的顶部。

堆栈溢出(StackOverflowError) public class Test{ public static void main(String[] args){ new Test().a(); } public void a(){ b(); } public void b(){ a(); } } //最开始,main()方法压入栈中,然后执行a(),a()押入栈中,在调用b(),b()押入栈栈中,以此往复,最终导致栈溢出 堆

​ 一个JVM只有一个堆内存(栈是线程级的),堆内存的大小是可以调节的,堆中存放实例化的对象。

堆内存详解

年轻代

对象的诞生、成长甚至死亡的区

Eden Space(伊甸园区):所有对象都是在此new出来的

Survivor Space(幸存区)

幸存0区(From Space),动态的From和To会互相交换

幸存1区(To Space)

Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。

老年代

Perm元空间

存储的是java运行时的一些环境或类信息,这个区域不存在垃圾回收。关闭虚拟机就会释放这个区域的内存,这个区域常驻内存,用来存放JDK自身携带的Class对象、Interface元数据。jdk1.8之前被称为永久代。

注意:元空间在逻辑上存在,在物理上不存在。新生代+老年代的内存空间=JVM分配的总内存。

什么是OOM

​ 内存溢出,产生原因:

分配的太少

用的太多

用完没释放

GC垃圾回收

​ 主要在年轻代和老年代。

​ 首先对象出生在伊甸园区,假设伊甸园区只能存在一定数量的对象,则每当存满时就会出发一次轻GC(Minor GC)。轻GC清理后,有的对象可能还存在引用,就活下来了,活下来的对象就进入幸存区;有的对象没用了,就被GC清理掉了;每次轻GC都会使得伊甸园区为空。

​ 如果幸存区和伊甸园区都满了,则会进入老年代,如果老年代满了,就会出发一次重GC(FullGC),年轻代+老年代的对象都会清理一次,活下来的对象都进入老年代。

​ 如果新生代和老年代都满了,则OOM。

Minor GC:伊甸园区满时触发,从年轻代回收内存

Full GC:老年代满时触发,清理整个堆空间

Major GC:清理老年代

​ 什么情况下永久区会崩?一个启动类加载了大量的第三方jar包,Tomcat部署了过多应用,或者大量动态生成的反射类,这些东西不断的被加载,知道内存满,就会出现OOM。

堆内存调优 查看并设置JVM堆内存 public class Test{ public static void main(String[] args){ //返回jvm试图使用的最大内存 long max = Runtime.getRuntime().maxMemory(); //返回jvm的初始化内存 long total = Runtime.getRuntime().totalMemory(); //默认情况下:分配的总内存为电脑内存的1/4,初始化内存为电脑内存的1/64 System.out.println("max=" + max / (double) 1024 / 1024 / 1024 + "G"); System.out.println("total=" + total / (double) 1024 / 1024 / 1024 + "G"); } }

​ 我们可以手动调整堆内存的大小,在VM options 中可以指定jvm试图使用的最大内存和jvm初始化内存的大小。

-Xms1024m -Xmx1024m -Xlog:gc*

-Xms用来设置jvm试图使用的最大内存

-Xmx用来设置jvm初始化内存

-Xlog:gc*用来打印GC垃圾回收信息

怎么排除OOM错误?

尝试扩大堆内存看结果

利用内存快照工具JProfiler

作用:分析Dump内存文件,快速定位内存泄漏;获得堆中的文件;获得大的对象

Dump文件是进程的内存镜像,可以把程序的执行状态通过调试器保存到dump文件中

import java.util.ArrayList; public class Test{ byte[] array = new byte[1024*1024];//1M public static void main(String[] args){ ArrayList<Test> list = new ArrayList<>(); int count = 0; try{ while(true){ list.add(new Test()); count++; } }catch(Exception e){ System.out.println("count="+count); e.printStackTrace(); } } }

运行程序,报错OOM。

接下来设置一下堆内存并附加生成dump文件的指令

-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

-XX:+HeapDumpOnOutOfMemoryError表示当JVM发生OOM时,自动生成DUMP文件。再次点击运行,下载了对应的Dump文件。

分析步骤:

右键该类,点击Show in Explorer

一直点击上级目录,直到找到.hprof文件

每次打开dump文件查看完后,建议删除,打开文件后生成了很多内容,占内存。

GC垃圾回收

​ 之前已经堆GC垃圾回收流程进行了大概的讲解:JVM在进行GC时,大部分回收都是在年轻代。

GC算法

引用计数法(很少使用)

每个对象在创建的时候,就给这个对象绑定一个计数器。

每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一;

这样,当没有引用指向该对象时,该对象死亡,计数器为0,这时就应该对这个对象进行垃圾回收操作。

复制算法

复制算法主要发生在年轻代(幸存0区和幸存1区)

当Eden区满的时候,会触发轻GC,每触发一次,活的对象就被转移到幸存区,死的对象就被GC清理掉,所以每次触发轻GC时,Eden区就会清空

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspppz.html