本文目录

  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

标记-整理+复制

服务端

并行与并发

  1. 并行与并发
  2. 分代收集
  3. 空间整合
  4. 可预测的停顿

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内存机制与垃圾收集器总结的更多相关文章

  1. JVM内存模型及垃圾收集策略解析(一)

    JVM内存模型是Java的核心技术之一,之前51CTO曾为大家介绍过JVM分代垃圾回收策略的基础概念,现在很多编程语言都引入了类似Java JVM的内存模型和垃圾收集器的机制,下面我们将主要针对Jav ...

  2. JVM的7种垃圾收集器:主要特点 应用场景 设置参数 基本运行原理

    原文地址:https://blog.csdn.net/tjiyu/article/details/53983650 下面先来了解HotSpot虚拟机中的7种垃圾收集器:Serial.ParNew.Pa ...

  3. JVM内存管理------垃圾搜集器参数精解

    本文是GC相关的最后一篇,这次LZ只是罗列一下hotspot JVM中垃圾搜集器相关的重点参数,以及各个参数的解释.废话不多说,这就开始. 垃圾搜集器文章传送门 JVM内存管理------JAVA语言 ...

  4. JVM系列三(垃圾收集器).

    一.概述 1. 哪些内存需要回收 上篇文章 我们介绍了 Java 内存运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈三个区域随线程而生,随线程而灭,在这几个区域内就不需要过多考虑回收的问题 ...

  5. JVM虚拟机-垃圾回收机制与垃圾收集器概述

    目录 前言 什么是垃圾回收 垃圾回收的区域 垃圾回收机制 流程 怎么判断对象已经死亡 引用计数法 可达性分析算法 不可达的对象并非一定会回收 关于引用 强引用(StrongReference) 软引用 ...

  6. 从Java虚拟机的内存区域、垃圾收集器及内存分配原则谈Java的内存回收机制

    一.引言: 在Java中我们只需要轻轻地new一下,就可以为实例化一个类,并分配对应的内存空间,而后似乎我们也可以不用去管它,Java自带垃圾回收器,到了对象死亡的时候垃圾回收器就会将死亡对象的内存回 ...

  7. JVM系列2:垃圾收集器与内存分配策略

    垃圾收集是一个很大话题,本文也只是看了深入理解Java虚拟机总结了下垃圾收集的知识. 首先按照惯例,先上思维导图: 垃圾收集简而言之就是JVM帮我们清理掉内存区域不需要的数据.它主要负责清理堆中实例对 ...

  8. JVM基础知识2 垃圾收集器与内存分配策略

    如何判断堆中的哪些对象可以被回收 主流的程序语言都是使用根搜索算法(GC Roots Tracing)判定对象是否存活 基本思路是:通过一系列名为“GC Roots”的对象作为起点,从这些节点开始向下 ...

  9. 【深入理解JAVA虚拟机】第二部分.内存自动管理机制.3.垃圾收集器与内存分配策略

    1.学习目的 当需要排查各种内存溢出. 内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节. Java内存运行时区域的各个部分,其中程序计数 ...

随机推荐

  1. rest-frameword框架的基本组件

    序列化 1.models部分 from django.db import models # Create your models here. class Book(models.Model): tit ...

  2. PHP与ECMAScript_2_数据类型

    PHP ECMAScript 数据类型 基本:String.Integer.Float.Boolean 基本:String. Number. Boolean.NULL.undefined 复合:Arr ...

  3. (读论文)推荐系统之ctr预估-NFM模型解析

    本系列的第六篇,一起读论文~ 本人才疏学浅,不足之处欢迎大家指出和交流. 今天要分享的是另一个Deep模型NFM(串行结构).NFM也是用FM+DNN来对问题建模的,相比于之前提到的Wide& ...

  4. Git-命令行-使用 git stash 暂存代码

    为什么我们需要它不得不说,在知道这个命令的时,以及之后的使用中,我都超级热爱这个命令,因为它真的太好用了. 给大家说一下我使用这个命令的场景: 此时我在 feature_666 分支,非常聚精会神加持 ...

  5. unimrcp-voice-activity语音检测

    研究 unimrcp有一段时间了,其中unimrcp voice acitve的算法,是遭到大家频繁吐槽.今天我们简单的介绍一下unimrcp voice activity 的这个简单粗暴的算法: u ...

  6. CEPH 对象存储的系统池介绍

    RGW抽象来看就是基于rados集群之上的一个rados-client实例. Object和pool简述 Rados集群网上介绍的文章很多,这里就不一一叙述,主要要说明的是object和pool.在r ...

  7. HackBar收费版绕过

    一段时间没用HackBar,近期做渗透,打开火狐浏览器,按F12键调出HackBar,发现居然需要收费买license才能使用. 经过研究,整理了以下两个绕过HackBar收费版的方法. 第一种:用其 ...

  8. Redhat 离线安装 Docker (Community from binaries)

    需求 在离线环境安装Docker (Community版),因为Enterprise版要花钱.当然资金充裕的客户可参考https://docs.docker.com/install/linux/doc ...

  9. 神奇的 SQL 之子查询,细节满满 !

    前言 开心一刻 有一天,麻雀遇见一只乌鸦. 麻雀问:你是啥子鸟哟 ? 乌鸦说:我是凤凰. 麻雀说:哪有你龟儿子这么黢黑的凤凰 ? 乌鸦说:你懂个铲铲,老子是烧锅炉的凤凰. 子查询 讲子查询之前,我们先 ...

  10. Java模拟并解决缓存穿透

    什么叫做缓存穿透 缓存穿透只会发生在高并发的时候,就是当有10000个并发进行查询数据的时候,我们一般都会先去redis里面查询进行数据,但是如果redis里面没有这个数据的时候,那么这10000个并 ...