JVM内存机制与垃圾收集器总结
本文目录
1. JVM内存组成结构
2. JVM内存回收
3. 垃圾收集器与算法
4. jdk1.6中class文件结构
5. jdk1.8中永久代与元空间比较
1. JVM内存组成结构
JVM栈由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:
2. JVM内存回收
Sun的JVMGenerationalCollecting(垃圾回收)原理是这样的:把对象分为新生代(Young)、老年代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析),即分代收集算法。
a. Young(新生代)
新生代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor取过来的对象。而且,Survivor区总有一个是空的。
eden、form、to的默认比例为8:1:1
b. Tenured(老年代):
老年代存放从新生代存活的对象。一般来说老年代存放的都是生命期较长,存活率高的对象。
c. Perm(持久代)(jdk1.8中已废弃)
用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。
举个例子:当在程序中生成对象时,正常对象会在新生代中分配空间,如果是过大的对象也可能会直接在老年代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在老年代分配)。新生代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。
通常我们说的jvm内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的新生代和老年代都是指的jvm的Heap空间,而持久代则是之前提到的MethodArea,不属于Heap。
3. 垃圾收集器与算法
JVM首先根据分代收集算法,在进行选择以下3种算法:
算法 |
适合年代 |
特点 |
标记-清除 |
老年代 |
对象存活率高,没有额外空间担保 效率低、产生很多不连续的碎片 |
复制 |
新生代 |
将可用内存折半,只使用一半,存活率低 |
标记-整理 |
老年代 |
对象存活率高,没有额外空间担保 |
收集器总结:
收集器名称 |
适合年代 |
算法 |
单线程/多 |
客户端/服务 |
串行/并行/并发 |
特点 |
Serial |
新生代 |
复制算法 |
单 |
客户端 |
串行 |
无线程交互,单线程内效率高 |
Par new |
新生代 |
复制算法 |
多 |
服务端 |
并行 |
Serial的多线程版本,服务端首选,可以与CMS配合 |
Parallel Scavenge |
新生代 |
复制算法 |
多 |
服务端 |
并行 |
可控制吞吐量,适合在后台运算不需要太多交互的场合 |
Serial old |
老年代 |
标记-整理 |
单 |
客户端 |
串行 |
Serial的老年代 |
Parallel old |
老年代 |
标记整理 |
多 |
服务端 |
并行 |
Parallel scav的老年版,ps+ps old组合使用 |
CMS |
老年代 |
标记-清除 |
多 |
服务端 |
并发 |
获取最短停顿时间,重视服务的响应速度。 工作流程: 初始标记>并发标记>重新标记>并发清除 |
G-First |
独立区域Region |
标记-整理+复制 |
多 |
服务端 |
并行与并发 |
|
4. jdk1.6中class文件结构
根据java虚拟机规范的规定,class文件格式采用一种类似于C语言结构体的伪结构体存储数据,分为两种数据类型:无符号数和表。
无符号数:属于基本的数据类型,以u1/u2/u4/u8分别代表1个、2个、4个、8个字节,主要用来描述数字、索引引用、数量值和字符串值。
表:由多个无符号数或者其他表作为数据项构成的复合数据类型,以_info结尾,用于描述有层次关系的复合结构的数据,整个class相当于一个表。
表的构成分为:
a. 魔数与class文件的版本:魔数用来区分class文件是否能把虚拟机接受;
b. 常量池:由字面量和符号引用构成
b.1 字面量:类似于java层面的常量
b.2 符号引用:由全限定名、字段名称和描述符合方法名称和描述符组成。
全限定名:类的路径,将'.'变成‘/’代替;
描述符:其实为字段类型和方法参数以及返回值;
c. 访问标志:区分是类或接口,是否是public、static、abstract;
d. 索引:分为类索引、父类索引和接口索引集合,用来确定类的继承关系;
e. 字段表:用于描述接口或者类里声明的变量,不包含方法里的局部变量;
f. 方法表:包含访问标志、名称索引、描述符、属性表;
g. 属性表:包含code属性、exceptions属性、constantValue属性,code用来存放方法代码经过编译变成的字节码,exceptions表示throws声明的异常,constantValue为来修饰静态变量。
5. jdk1.8中永久代与元空间比较
移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。我们可以通过一段程序来比较 JDK 1.6 与 JDK 1.7及 JDK 1.8 的区别,以字符串常量为例:
package com.xs.test.memory;
import java.util.ArrayList;
import java.util.List;
public class StringOomMock {
static String base = "string";
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i=0;i< Integer.MAX_VALUE;i++){
String str = base + base;
base = str;
list.add(str.intern());
}
}
}
这段程序以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存。我们通过 JDK 1.6、JDK 1.7 和 JDK 1.8 分别运行:
JDK 1.6 的运行结果:
JDK 1.7的运行结果:
JDK 1.8的运行结果:
从上述结果可以看出,JDK 1.6下,会出现“PermGen Space”的内存溢出,而在 JDK 1.7和 JDK 1.8 中,会出现堆内存溢出,并且 JDK 1.8中 PermSize 和 MaxPermGen 已经无效。因此,可以大致验证 JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中,并且 JDK 1.8 中已经不存在永久代的结论。现在我们看看元空间到底是一个什么东西?
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
现在我们在 JDK 8下重新运行一下代码段 4,不过这次不再指定 PermSize 和 MaxPermSize。而是指定 MetaSpaceSize 和 MaxMetaSpaceSize的大小。输出结果如下:
从输出结果,我们可以看出,这次不再出现永久代溢出,而是出现了元空间的溢出。
总结
通过上面分析,大家应该大致了解了 JVM 的内存划分,也清楚了 JDK 8 中永久代向元空间的转换。不过大家应该都有一个疑问,就是为什么要做这个转换?所以,最后给大家总结以下几点原因:
1、字符串存在永久代中,容易出现性能问题和内存溢出。
2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4、Oracle 可能会将HotSpot 与 JRockit 合二为一。
本文为博主原创,转载请注明:https://www.cnblogs.com/jiangds/p/11425602.html
JVM内存机制与垃圾收集器总结的更多相关文章
- JVM内存模型及垃圾收集策略解析(一)
JVM内存模型是Java的核心技术之一,之前51CTO曾为大家介绍过JVM分代垃圾回收策略的基础概念,现在很多编程语言都引入了类似Java JVM的内存模型和垃圾收集器的机制,下面我们将主要针对Jav ...
- JVM的7种垃圾收集器:主要特点 应用场景 设置参数 基本运行原理
原文地址:https://blog.csdn.net/tjiyu/article/details/53983650 下面先来了解HotSpot虚拟机中的7种垃圾收集器:Serial.ParNew.Pa ...
- JVM内存管理------垃圾搜集器参数精解
本文是GC相关的最后一篇,这次LZ只是罗列一下hotspot JVM中垃圾搜集器相关的重点参数,以及各个参数的解释.废话不多说,这就开始. 垃圾搜集器文章传送门 JVM内存管理------JAVA语言 ...
- JVM系列三(垃圾收集器).
一.概述 1. 哪些内存需要回收 上篇文章 我们介绍了 Java 内存运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈三个区域随线程而生,随线程而灭,在这几个区域内就不需要过多考虑回收的问题 ...
- JVM虚拟机-垃圾回收机制与垃圾收集器概述
目录 前言 什么是垃圾回收 垃圾回收的区域 垃圾回收机制 流程 怎么判断对象已经死亡 引用计数法 可达性分析算法 不可达的对象并非一定会回收 关于引用 强引用(StrongReference) 软引用 ...
- 从Java虚拟机的内存区域、垃圾收集器及内存分配原则谈Java的内存回收机制
一.引言: 在Java中我们只需要轻轻地new一下,就可以为实例化一个类,并分配对应的内存空间,而后似乎我们也可以不用去管它,Java自带垃圾回收器,到了对象死亡的时候垃圾回收器就会将死亡对象的内存回 ...
- JVM系列2:垃圾收集器与内存分配策略
垃圾收集是一个很大话题,本文也只是看了深入理解Java虚拟机总结了下垃圾收集的知识. 首先按照惯例,先上思维导图: 垃圾收集简而言之就是JVM帮我们清理掉内存区域不需要的数据.它主要负责清理堆中实例对 ...
- JVM基础知识2 垃圾收集器与内存分配策略
如何判断堆中的哪些对象可以被回收 主流的程序语言都是使用根搜索算法(GC Roots Tracing)判定对象是否存活 基本思路是:通过一系列名为“GC Roots”的对象作为起点,从这些节点开始向下 ...
- 【深入理解JAVA虚拟机】第二部分.内存自动管理机制.3.垃圾收集器与内存分配策略
1.学习目的 当需要排查各种内存溢出. 内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节. Java内存运行时区域的各个部分,其中程序计数 ...
随机推荐
- MySql的数据库优化到底优化啥了都(3)
嘟嘟在上两个文章里面简单粗糙的讲了讲关于MySql存储引擎的一些特性以及选择.个人感觉如果面试官给我机会的话,至少能说个10分钟了吧.只可惜有时候生活就是这样:骨感的皮包骨头了还在那美呢.牢骚两句,北 ...
- spring boot入门篇
Spring Boot[快速入门] Spring Boot 概述 Build Anything with Spring Boot:Spring Boot is the starting point ...
- 使用redis分布式锁解决并发线程资源共享问题
众所周知, 在多线程中,因为共享全局变量,会导致资源修改结果不一致,所以需要加锁来解决这个问题,保证同一时间只有一个线程对资源进行操作 但是在分布式架构中,我们的服务可能会有n个实例,但线程锁只对同一 ...
- CMS简单内容管理系统
架构 NewsDaoSQLServerImpl public class NewsDaoSQLServerImpl extends BaseDao implements NewsDao { publi ...
- Java1.8新特性实战
public class JDK8_features {private ArrayList<Integer> list; @Testpublic void test(){/*** 1.La ...
- drf之序列化
在django视图中使用serializer 只是使用serializer类编写API视图,没有用到REST框架 app01下的models.py from django.db import mode ...
- 夯实Java基础(七)——Static关键字
1.static介绍 static关键字一直是各大企业中面试常常会问到的问题,主要考察面试者的基础是否扎实,下面来介绍一下static关键字. Java中static表示“全局”或者“静态”的意思,可 ...
- 【Java例题】6.2 日期类的使用
2.日期类的使用.显示今天的年月日.时分秒和毫秒数.显示今天是星期几.是今年内的第几天.显示本月共几天,今年是不是闰年.显示两个日期的差,包括年月日.时分秒和毫秒差值. package chapter ...
- Hystrix超时测试
package com.cookie.test; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.Hystr ...
- java并发编程(十六)----(线程池)java线程池的使用
上节我们简单介绍了线程池,这次我们就来使用一下.Executors提供四种线程池,分别是:newCachedThreadPool,newFixedThreadPool ,newScheduledThr ...