本文目录

  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. java中的堆、栈、方法区等比较

    • 堆.栈.方法区 1. java中的栈(stack)和堆(heap)是java在内存(ram)中存放数据的地方 2. 堆区 存储的全部是对象,每个对象都包含一个与之对应的class的信息.(clas ...

  2. django中ORM的model对象和querryset 简单解析

    欢迎大家查看我的博客,我会不定时的用大白话发一些看了就能懂的文章,大家多多支持!如您对此文章内容有独特见解,欢迎与笔者练习一起探讨学习!原创文创!转载请注明出处! ORM是干嘛的? 介绍orm之前我应 ...

  3. Linux系统安装jdk——.tar.gz版(old)

    这里简单地阐述一下rpm.deb.tar.gz的区别. rpm格式的软件包适用于基于Red Hat发行版的系统,如Red Hat Linux.SUSE.Fedora. deb格式的软件包则是适用于基于 ...

  4. net core 序列化与反序列化与遇到的几个坑

    之前在C#里面序列化直接引入命名空间后使用JavaScriptSerializer jss = new JavaScriptSerializer();就可以用, 而net core里面不这样用了,我们 ...

  5. 湫湫系列故事——设计风景线 HDU - 4514

    题目链接:https://vjudge.net/problem/HDU-4514 题意:判断没有没有环,如果没有环,通俗的讲就是找出一条最长的路,相当于一笔画能画多长. 思路:dfs判环. 最后就是没 ...

  6. js中数组和对象的合并

    1 数组合并 1.1 concat 方法 1 2 3 4 var a=[1,2,3],b=[4,5,6]; var c=a.concat(b); console.log(c);// 1,2,3,4,5 ...

  7. DesignPattern系列__06迪米特原则

    迪米特原则定义 迪米特原则,也叫最少知道原则,即一个类应该对自己依赖的类知道的越少越好,而你被依赖的类多么复杂,对我都没有关系.也就是说,对于别依赖的类来说,不管业务逻辑多么复杂,都应该尽量封装在类的 ...

  8. SpringMVC项目案例之---数据的获取与显示

    数据的获取与显示 (一)功能 1.对用户输入的数据进行获取 2.将获取的数据显示到页面 3.使用了SpringMVC技术的注解方式 4.使用了过滤器,处理中文乱码问题 5.在web.xml中设置了访问 ...

  9. 让techempower帮你通讯服务框架的性能

    在编写服务应用框架的时候一般都需要进行性能测试,但自己测试毕竟资源受限所以很难做更高性能上的测试.其实GitHub上有一个项目可以让开发人员提交自己的框架服务代码然后进行一个标准测试:现在已经有上百个 ...

  10. 佳木斯集训Day1

    23333第一次写博客 其实在佳木斯集训之前我都已经两三个月没打代码了 在佳木斯的时候前几天真心手生,导致了前几次考试考的很差... D1的考试还是比较良心的,T1是一道大模拟,直接枚举最后几位是00 ...