辽宁建设网站,知乎网站建设入门书,wordpress 修改文档目录名,免费空间可以上传网站吗JVM内存模型
对于 Java 程序员来说#xff0c;在虚拟机自动内存管理机制下#xff0c;不再需要像 C/C 程序开发程序员这样为每一个操作去写对应的 delete / free 操作#xff0c;不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序把内new存控制权利交给JVM虚拟机。一旦…JVM内存模型
对于 Java 程序员来说在虚拟机自动内存管理机制下不再需要像 C/C 程序开发程序员这样为每一个操作去写对应的 delete / free 操作不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序把内new存控制权利交给JVM虚拟机。一旦出现内存泄漏和溢出方面的问题如果不了解虚拟机是怎样使用内存的那么排查错误将会是一个非常艰巨的任务。
JVM 虚拟机在执行 java 程序的过程中会把它管理的内存划分成若干个不同的区域每个区域有各自的不同的用途、创建方式及管理方式。有些区域随着虚拟机的启动一直存在有些区域则随着用户线程的启动和结束而建立和销毁这些共同组成了 Java 虚拟机的运行时数据区域也被称为 JVM 内存模型
运行时数据区域划分
JVM虚拟机在执行JAVA程序的过程中会把它管理的内存划分成若干个不同的数据区域由方法区堆区虚拟机栈本地方法栈程序计数器五部分组成 版本的差异
JDK 1.8之前分为线程共享Heap堆区、Method Area 方法区、线程私有虚拟机栈、本地方法栈、程序计数器JDK 1.8之后分为线程共享Heap堆区、MetaSpace员工间、线程私有虚拟机栈、本地方法栈、程序计数器 其中虚拟机栈、本地方法栈、程序计数器是线程私有的区域所以随着线程消亡而结束。而线程共享的 Heap 堆区、MetaSpace 元空间会随着虚拟机的启动一直存在。
程序计数器
Program Counter Register‘
程序计数器是一块较小的内存空间是当前线程所执行的字节码的行号指示器
字节码解释器在解释执行字节码文件工作时每当需要执行一条字节码指令时就通过改变程序计数器的值来完成.程序中的分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。 程序执行过程中会不断的切换当前执行线程切换后为了能让当前线程恢复到正确的执行位置每条线程都需要有一个独立的程序计数器并且各线程之间计数器互不影响独立存储 程序计数器主要作用
字节码解释器通过改变程序计数器来一次读取命令从而实现代码的流程控制如顺序执行选择循环。异常处理在多线程的情况下程序计数器用于记录当前线程执行的位置从而当线程被切换回来的时候能够知道当前现成的运行位置恢复当前线程的执行
程序计数器是唯一一个不会出现OutOfMemoryError的内存区域它随着现成的创建而创建随着线程的结束而死亡
Java虚拟机栈
VM Stack
虚拟机栈是线程执行Java程序时处理Java方法中内容的区域虚拟机栈也是线程私有的区域每个Java方法被调用的时候都会在虚拟机栈中创建一个栈顶而每个栈帧又由局部变量操作数栈动态链接和方法返回四部分组成有些虚拟机的栈帧还包括了一些附加的信息 JMM内存区域可以粗略的氛围堆内存Heqp和栈内存Stack其中栈就是VM Stack虚拟机栈或者说是虚拟机栈中局部变量表部分
局部变量表主要存放了编译器可知的各种基本数据类型变量值boolean、byte、char、short、int、float、long、double对象应用reference类型它不同于对象本身可能是一个指向对象起始地址的引用指针也可能是指向一个代表对象的句柄或其他与此对象相关的位置 虚拟机栈运行原理
每一次方法调用都会有一个对应的栈帧被压入VM Stack 虚拟机栈每一个方法调用结束后代表该方法的栈帧会从VM Stack虚拟机栈中弹出
虚拟机栈是内存的私有区域并且栈帧不允许被其他线程访问不存在线程安全问题栈帧弹出后内存就会被系统回收所以也不存在垃圾回收问题 在活动线程中只有位于栈顶的栈才是有效的成为当前活动栈帧代表正在执行的当前方法
在JVM执行引擎运行时所有指令都只能对当前活动栈帧进行操作。虚拟机栈通过pop和push的方式对每个方法对应的活动栈帧进行运算处理方法正常执行结束肯定会跳转到另一个栈帧上 活动栈帧被弹出的方式
java方法有两种返回方式不管哪种返回方式都会导致当前活动栈帧被弹出
return 语句抛出异常
虚拟机栈可能产生的错误
java虚拟机栈会出现两种错误StackOverFlowError和OutOfMemoryError
StackOverFlowError当线程请求栈的深度超过JVM虚拟机栈的最大深度的时候就抛出StackOverFlowError错误OutOfMemoryErrorJVM的内存大小可以动态扩展如果虚拟机的动态扩展栈时无法申请到足够的内存空间则抛出OutOfMemoryError异常
虚拟机栈的大小
虚拟机栈的大小可以通过-Xss参数设置默认单位是byte也可以使用k,m,g作为单位不区分大小写。例如-Xss 1m
在不同的操作系统下-Xss的默认值不同
Linux1024KMacOs1024KWindows默认值依赖于虚拟机的内存
本地方法栈
Native Method Stack
native关键字修饰的本地方法被执行的时候在本地方法栈中会创建一个栈帧用于存放该native本地方法的局部变量表、操作数栈、动态链接、方法出口信息。方法执行完毕后相应的栈帧也会出栈且释放内存空间。也会出现StackOverFlowError和OutOfMemoryError两种错误
★★堆(Heap)★★
Heap 堆区用于存放对象实例和数组的内存区域
Heap 堆区是 JVM 所管理的内存中最大的一块区域被所有线程共享的一块内存区域。堆区中存放对象实例“几乎”所有的对象实例以及数组都在这里分配内存。 每一个 JVM 进程只存在一个堆区它在 JVM 启动时被创建 JVM 规范中规定堆区可以是物理上不连续的内存但必须是逻辑上连续的内存。每一个 JVM 进程只存在一个堆区它在 JVM 启动时被创建 JVM 规范中规定堆区可以是物理上不连续的内存但必须是逻辑上连续的内存。 每一个 JVM 进程只存在一个堆区它在 JVM 启动时被创建 JVM 规范中规定堆区可以是物理上 不连续的内存但必须是逻辑上连续的内存。 JVM 规范中描述所有的对象实例及数组都应该在运行时分配在堆上。而他们的引用会被保存在 虚拟机栈中当方法结束这些实例不会被立即清除而是等待 GC 垃圾回收。 由于堆占用内存大所以是 GC 垃圾回收的重点区域因此堆区也被称作 GC堆 Garbage Collected Heap 对象逃逸分析
Java 世界中“几乎”所有的对象都在堆中分配但是随着 JIT 编译器的发展与逃逸分析技术逐渐成熟栈上分配、标量替换优化技术将会导致一些微妙的变化所有的对象都分配到堆上也渐渐变得不那么“绝对”了
从 JDK 1.7 开始已经默认开启逃逸分析如果某些方法中的对象引用没有被返回或者未被外面使用 (也就是未逃逸出去)那么对象可以直接在栈上分配内存
堆的组成新生代老年代
从垃圾回收的角度由于现在收集器基本都采用粉黛垃圾收集算法所以JVM中的堆区往往进行粉黛划分例如新生代和老年代。目的是为了更好地回收内存或者更快地分配内存 堆区的组成分为新生代Young Generation老年代Old Generation。 新生代被分为伊甸区Eden和幸存者区from to幸存区又被分为 Survivor 0from和Survivor 1to 新生代和老年代的比例为12伊甸区和S0、S1比例为811不用区域存放对象的用途和方式不同 伊甸区Eden存放大部分新创建对象幸存区Survivor存放Minor GC 之后Eden区和幸存区Survivor本身没有被回收的对象老年代存放Minor GC之后且年龄计数器达到15此信息存在与对象头中依然存活的对象Major GC和Full GC之后仍然存活的对象 对空间大小设置
堆区的内存大小是可以修改的默认轻快下初始堆内为物理内存的1/64最大的物理内存的1/4
-Xms设置初始化堆内存例如-Xms64m-Xmx设置最大堆内存例如-Xmx64m-Xmn设置年轻代内存例如-Xmx32m Heap堆区中的新生代、老年代的空间分配比例可以通过java -XX:PrintFlagsFinal -version命令查看 uintx InitialSurvivorRatio 8 新生代Young(Eden/Survivor)空间的初始比例 8代表Eden占新生代空间的80% uintx NewRatio 2 老年代Old/新生代Young的空间比例 2代表老年代Old是新生代Young的2倍
因为新生代是由Edens0s1组成的所以按照上述默认比例如果Enen区内存大小是40M那么两个Survivor区就是5M整个新生代区就是50M然后可以算出老年代Old区内存大小是100M堆区总大小就是150M
创建对象的内存分配★★★★★
创建一个新对象在堆内的分配内存
大部分情况下对象会在Eden区生成当Eden区装填满的时候会触发Young Garbage Collection即YGC垃圾回收的时候
依然存活的对象会被移送到Survivor区。Survivor区分为S0和S1两块内存区域。每次YGC的时候它们将存活的对象复制到未使用的Survivor空间S0或S1然后将当前正在使用的空间完全清楚交换两块空间的使用状态。每次交换时对象的年龄就会1
如果YGC要以送的对象大于Survivor区容量的上线则直接移交给老年代。一个对象也不可能永远呆在新生代在JVM中一个对象从新生代晋升到老年代的阈值默认值是15可以在Survivor区交换14次后晋升至老年代 堆区的分代垃圾收集思想
出于效率的缘故JVM 的垃圾收集不会对三个区域(伊甸区、幸存区、老年代)进行收集大部分时候都是回收新生代 HotSpot 虚拟机将垃圾收集分为部分收集( Partial GC )和整堆收集( FulI GC
部分收集
新生代收集YGCMinor GC/Young GC回收新生代区域频率比较高因为大部分对的存活寿命比较短在新生代里被回收性能耗费较小。例如Serial、ParNew、Parallel Scavenge等垃圾收集器都是新生代收集老年代收集Old GC回收老年代区域例如Serial Old、CMS、Parallel Old等垃圾回收器都是老年代收集混合收集Mixed GC收集整个年轻代区域及部分老年代区域目前只有G1收集器有
**整机收集FGCFull GC**回收整个Java堆区默认堆空间使用带到80%可调整的时候会触发FGC。频率根据访问量的多少决定可能十天也可能一周左右一次整机收集的频率越少越好
GC组合垃圾回收只有YGC和FullGC、OldGC不可以单执行。原因是OldGC是STW机制标记整理算法相对耗时只能在关键时刻使用因此只有FullGC才能出发OldGC GC垃圾回收的影响
GC耗时太长、GC次数太多会影响进程的性能、导致进程相应变慢、或者无法响应
YGC耗时耗时在几十或者几百毫秒属于正常情况用户几乎无感知对程序影响比较少、耗时太长或者频繁、会导致服务器超时问题YGC次数太频繁、会降低服务的整机性能、高并发服务时、影响会比较大FGC次数越少越好。比较正常的情况几小时一次或者几天一次FGC耗时耗时很长会导致线程频繁被停止使应用响应变慢比较卡顿 产生FGC的原因
大对象: 系统一次性加载了过多数据到内存中导致大对象进入了老年代内存泄漏: 频繁创建了大量对象但是无法被回收 (比如 流对象使用完后未调用 lose 方法释放资源) 先引发 FGC 最后导致 OOM 。程序频繁生成一些长生命周期的对象当这些对象的存活年龄超过分代年龄时便会进入老年代最后引发 FGC程序 BUG 导致动态生成了很多新类使得 Metaspace 不断被占用先引发 FGC 最后导致 OOMJVM 参数设置不合理: 包括总内存大小、新生代和老年代的大小、 Eden 区和 Survivor 区的 大小、元空间大小、垃圾回收算法等等
堆区产生的错误
堆区最容易出现的就是OutOfMemoryError错误这种错误的表现形式有以下两种
OutOfMemoryError:GC Overhead Limit Exceeded当JVM花太多时间执行垃圾回收、并且只能回收很少的堆空间时、就会发生此错误OutOfMemoryErrorJava heap space如果在创建新的对象时堆内存中的空间不足以存放新创建的对象就会引发此错误
此种情况与配置的最大堆内存有关且受限制于物理内存大小
元空间(MetaSpace)
用于存放类信息、常量、静态常量、JIT即时编译器编译后的机器代码等数据等/例如java.lang.Object类的原喜喜、Integer.MAX_VALUE常量等
JDK1.6
HotSpot JVM使用Method Area方法去存储也叫永久代Permanent Generation
1.方法去和永久代(Permanent Generation)的区别方法去是JVM的规范。而永久代是JVM规范的一种实现并且只有HotSpot JVM才有永久代而对于其他类型的虚拟机如JRockit(ORacle)、J9(IBM)并没有
2.方法区是一片连续的堆空间当JVM加载的类信息容量超过了最大可分配空间虚拟机会抛出OutOfMemoryErrorPermGenSpace的Error
3.永久代的GC是和老年代(old generation)捆绑在一起无论谁满了都会出发永久代和老年代的垃圾收集
4.可以通过 -XX:PermSizeN 设置方法区(永久代)初始化空间-XX:MaxPermSizeN 设置方法区永久代最大空间超过这个值将会抛出错误java.lang.OutOfMemoryError:PermGenJDK1.7
1.7是一个过度版本
将字符串常量池、静态变量转移到了堆区JDK1.8正式移除永久代采用Meta Space元空间代替
元空间的本质和永久代类似都是对 JVM 规范中方法区的一种具体实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中而是使用本地内存。因此默认情况下元空间的大小仅受本地内存限制但可以通过运行参数来指定元空间的大小。java 8 中PermGen永久代被移出HotSpot JVM的原因
1.由于PermGen内存经常会移除容易抛出java,lang.OutOfMemoryError: PermGen错误
2.移除PermGen可以促进HotSpot JVM 与 JRockit VM 的融合因为JRockit没有永久代 上述运行结果可以看出相同的代码在JDK1.6会出现PermGen Space的永久代内存移除而从JDK1,7和JDK1.8会出现Java heap space 堆内存移除并且JDK1.8中PermSize和MaxPermGen参数已经无效。因此在JDK1.7和JDK1.8中已经将字符串常量由永久代转移到堆中并且JDK1.8已经完全移除了永久代采用元空间来替代 1.-XX:MetaspaceSize参数主要控制Meta Space GC发生的初始阈值也就是最小阈值当使用的Meta Space空间到达MetaspaceSize的时候就会触发Metaspace的GC
2.-XX:MaxMetaspaceSize参数最大空间默认是没有限制的。在jvm启动的时候并不会分配MaxMetaspaceSize这么大的一块内存出来metaspace是可以一直扩容的知道到达MaxMetasoaceSize字符串常量池 String的两种创建方式 第一种方式是在常量池中获取字符串对象第二种方式是直接在对内存空间创建一个新的字符串对象 // 先检查字符串常量池中有没有abc如果字符串常量池中没有则创建一个然后str1指向字符串常量池中的对象如果有则直接将str1指向abc
String str1 abc;
String str2 new String(abc); //堆中创建一个新的对象
String str3 new String(abc); //堆中创建一个新的对象System.out.printf(str1 str2); //false
System.out.printf(str2 str3); //falseString的intern()方法 检查指定字符串在常量池中是否存在如果存在则返回地址如果不存在则在常量池中创建 String s1 new String(abc);
String s2 s1.intern(); //查看字符串常量池中是否存在abc,如果存在则返回地址如果不存在则在常量池中创建
string s3 abc; // 使用常量池中的已有的字符串常量abcSystem.out.printf(str2 str3); //trueString的拼接 String s1 str;
String s2 ing;String s3 string; // 常量池中的新字符串对象
String s4 str1 str2; // 在堆中创建的新字符串对象
String s5 string; // 常量池中的已有字符串对象System.out.printf(str3 str4); //false
System.out.printf(str3 str5); //true
System.out.printf(str4 str5); //falseString s1 new String(abc);这句代码创建了几个字符串对象 创建1或2个字符串如果常量池中已存在字符串常量abc,则只会在堆空间创建一个字符串常量abc。如果常量池中没有字符串常量abc,那么它将首先在池中创建然后再堆空间中创建因此将创建总共2个字符串对象