jvm内存模型

程序计数器

Java(虚拟机栈)

本地方法栈

Java

方法区及其运行时常量池

垃圾回收机制

u  新生代和老年代

u  参数设置

u  垃圾回收(Minor GC 和 Full GC)和回收算法

u  finalize()、减少GC开销、触发主GC的条件

u  判断无用对象、四种引用方式、为什么进行垃圾回收

u  String、StringBuffer与StringBuilder

u  类加载机制

类加载机制

一 : jvm内存模型

第一、程序计数器(PC)

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来取下一条需要执行的字节码指令

由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。

注:程序计数器是线程私有的,每条线程都会有一个独立的程序计数器

第二、Java栈(虚拟机栈)

Java栈就是Java中的方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(关于栈帧后面介绍),这个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

注:Java栈也是线程私有的。

异常可能性:对于栈有两种异常情况:如果线程请求的栈深度大于栈所允许的深度,将抛出StackOverflowError异常,如果虚拟机栈可以动态拓展,在拓展的时无法申请到足够的内存,将会抛出OutOfMemoryError异常

栈帧

1)  局部变量表

2)  操作数栈

3)  整数加法

4)  法返回地址

第三、本地方法栈

本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法。

注:本地方法栈也是线程私有的

异常可能性:和Java栈一样,可能抛出StackOverflowError和OutOfMemeryError异常

第四、Java堆

对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,当然我们后面说到的垃圾回收器的内容的时候,其实Java堆就是垃圾回收器管理的主要区域。

注:堆是线程共享的

异常可能性:如果堆中没有内存完成实例分配,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常

第五、方法区(如果一个系统不断地产生新的类,而没有回收,那最终非常有可能导致永久区溢出。)

方法区它用于存储已被虚拟机加载的类信息(类型信息,字段信息,方法信息,其他信息)静态量、即时编译器编译后的代码等数据。方法区是线程安全的

注:方法区和堆一样是线程共享的

异常可能性:当方法区无法满足内存分配需求时,将抛出OutOfMemeryError异常

运行时常量池

用于存放编译器生成的各种字面量和符号引用

参数配置

  • -Xmx3550m:设置JVM最大堆内存为3550M。
  • -Xms3550m:设置JVM初始堆内存为3550M。
  • -XX:NewSize=1024m:设置新生代初始值为1024M。
  • -XX:MaxNewSize=1024m:设置新生代最大值为1024M。
  • -XX:PermSize=256m:设置老年代初始值为256M。
  • -XX:MaxPermSize=256m:设置老年代最大值为256M。
  • -XX:NewRatio=4:设置年轻代(包括1个Eden和2个Survivor区)与年老代的比值。表示新生代比老年代为1:4。
  • -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的比值。表示2个Survivor区与1个Eden区的比值为2:4,即1个Survivor区占整个年轻代大小的1/6。

二 :Jvm垃圾回收

Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间。

堆的内存分配

新生代(Young Generation)(默认的Eden:Survivor = 8:1)

  1.所有新生成的对象首先都是放在新生代的。新生代的目标就是尽可能快速的收集掉那些生命周期短的对象。

  2.新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。

  3.当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收

4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)

在Minor GC时会将新生代中还存活着的对象复制进一个Survivor中,然后对Eden和另一个Survivor进行清理。所以,平常可用的新生代大小为Eden的大小+一个Survivor的大小。所有的Minor GC都会触发全世界的暂停(stop-the-world除了垃圾收集收集器线程之外的线程都被挂起),停止应用程序的线程,不过这个过程非常短暂。Eden 和Survivor区不存在内存碎片。

当对象在 Eden出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( to 区域 ) ,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定),这些对象就会成为老年代。

对于一些较大的对象即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代

在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)

年老代(Old Generation)

  1 . 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

  2 . 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Full GC  发生频率比较低,老年代对象存活时间比较长,存活率标记高。

什么情况下,新生代的对象会进入老年代呢?

当Minor GC时,新生代存活的对象大于Survivor的大小时,这时一个Survivor装不下它们,那么它们就会进入老年代。

在新生代的每一次Minor GC 都会给在新生代中的对象+1岁,默认到15岁时就会从新生代进入老年代-XX:MaxTenuringThreshold来设置这个临界点。

如果设置了-XX:PretenureSizeThreshold3M 那么大于3M的对象就会直接就进入老年代。

3.finalize()方法什么时候被调用?

l  垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法 但是在Java中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。 那么finalize()究竟是做什么的呢?

l  它最主要的用途是回收特殊渠道申请的内存。由于垃圾回收器只知道那些显示地经由new分配的内存空间,所以它不知道该如何释放这块“特殊”的内存区域,那么这个时候java允许在类中定义一个由 finalize()方法

3.减少GC开销的措施

(1)不要显式调用System.gc()

(2)尽量减少临时对象的使用

(3)对象不用时最好显式置为Null

(4)尽量使用StringBuffer,而不用String来累加字符串

2.触发主GC(Garbage Collector)的条件

(1)当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。

(2)Java堆内存不足时,GC会被调用。

一.如何确定某个对象是“垃圾”(无用对象)?

引用计数法

如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。循环引用对象将无法回收

可达性分析法

该方法的基本思想是通过一系列的根对象的集合作为起点进行搜索,从这些根对象开始,任何可以被触及的对象都是被认为是“活动”的对象。无法被触及的对象被认为是垃圾,因为它们不在影响程序的未来执行。

二.典型的垃圾收集算法

1.Mark-Sweep(标记-清除)算法

这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。

从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。

2.Copying(复制)算法

为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。问题是占用内存较多

3.Mark-Compact(标记-整理)算法

为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。

4.分代收集算法

一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。方法区永久代,回收方法同老年代。

老年代采用的是标记-清除或者标记-整理算法,这两个算法主要看虚拟机采用的哪个收集器,两种算法的区别是:标记-清除可能会产生大量连续的内存碎片。

三.String、StringBuffer与StringBuilder之间区别

String 字符串常亮,底层是数组实现的

StringBuilder:线程非安全的

StringBuffer:线程安全的

对于三者使用的总结:

1.如果要操作少量的数据用 = String

  2.单线程操作字符串缓冲区下操作大量数据 = StringBuilder

3.多线程操作字符串缓冲区下操作大量数据 = StringBuffer

四.为什么要进行垃圾回收

在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。 JVM的一个系统级线程会自动释放该内存块,减轻编程的负担。事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片。

五.java虚拟机类加载机制

其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。

l  加载

加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。

2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在就Java虚拟机中的唯一性

类加载器:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器

双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

l  验证

验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。

l  准备

准备阶段是正式为类变量分配内存并设置类变量初始值(零)的阶段,这些内存都将在方法区中分配。

l  解析

解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程。

l  初始化

初始化是类加载过程的最后一步,此阶段才开始真正执行类中定义的Java程序代码(静态语句块和构造器)

四.JAVA四种引用方式

强引用、软引用、弱引用、虚引用

l  强引用:当我们使用new 这个关键字创建对象时被创建的对象就是强引用,垃圾回收器就不会去回收有强引用的对象。

l  软引用:如果一个对象具备软引用,如果内存空间足够,那么垃圾回收器就不会回收它,如果内存空间不足了,就会回收该对象。当然没有被回收之前,该对象依然可以被程序调用。java.lang.ref.SoftReference

l  弱引用:如果一个对象只具有弱引用,只要垃圾回收器在自己的内存空间中线程检测到了,就会立即被回收,对应内存也会被释放掉。java.lang.ref.WeakReference

l  虚引用:如果一个对象只具有虚引用,那么它就和没有任何引用一样,随时会被jvm当作垃圾进行回收。虚引用目的:当对象被收集器回收时收到系统通知。java.lang.ref.PhantomReference

java虚拟机总结的更多相关文章

  1. 深入Java虚拟机--判断对象存活状态

    程序计数器,虚拟机栈和本地方法栈 首先我们先来看下垃圾回收中不会管理到的内存区域,在Java虚拟机的运行时数据区我们可以看到,程序计数器,虚拟机栈,本地方法栈这三个地方是比较特别的.这个三个部分的特点 ...

  2. 【深入Java虚拟机】之四:类加载机制

    类加载过程     类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 其中类加载的过程包括了加载.验 ...

  3. 《深入理解Java虚拟机》类文件结构

    上节学习回顾 在上一节当中,主要以自己的工作环境简单地介绍了一下自身的一些调优或者说是故障处理经验.所谓百变不离其宗,这个宗就是我们解决问题的思路了. 本节学习重点 在前面几章,我们宏观地了解了虚拟机 ...

  4. 《深入理解Java虚拟机》调优案例分析与实战

    上节学习回顾 在上一节当中,主要学习了Sun JDK的一些命令行和可视化性能监控工具的具体使用,但性能分析的重点还是在解决问题的思路上面,没有好的思路,再好的工具也无补于事. 本节学习重点 在书本上本 ...

  5. 《深入理解Java虚拟机》虚拟机性能监控与故障处理工具

    上节学习回顾 从课本章节划分,<垃圾收集器>和<内存分配策略>这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制.好让我们对JVM运行机制有一个良好的概念 ...

  6. JVM学习(1)——通过实例总结Java虚拟机的运行机制

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: JVM的历史 JVM的运行流程简介 JVM的组成(基于 Java 7) JVM调优参数:-Xmx和-Xms ...

  7. Elasticsearch Java 虚拟机配置详解

    Elasticsearch对Java虚拟机进行了预先的配置.通常情况下,因为这些配置的选择还是很谨慎的,所以你不需要太关心,并且你能立刻使用ElasticSearch. 但是,当你监视ElasticS ...

  8. 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码

    程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...

  9. Java虚拟机(JVM)以及跨平台原理详细的介绍

    相信大家已经了解到Java具有跨平台的特性,可以"一次编译,到处运行",在Windows下编写的程序,无需任何修改就可以在Linux下运行,这是C和C++很难做到的.那么,跨平台是 ...

  10. Java虚拟机浅探

    简介 对于java开发人员了来说,对java虚拟机肯定有着或多或少的了解.因为有了虚拟机的存在,才会使得java的内存管理变得那么方便,不再像C++那样使用new/delete来直接管理内存.知名的j ...

随机推荐

  1. vue学习笔记 实例(二)

    var data = {a: 1} var vm = new Vue({ el: '#example', data: data, created: function () { // `this` 指向 ...

  2. Django后台设置--遇到的问题与解决方案

    1. 后台如何管理项目中的models 新建的Django工程会自动引用admin 应用,新建后台可以通过 createsuperuser 命令建立后台admin超级管理员,我遇到的第一个问题,就是如 ...

  3. h5开发app之在线生成二维码

    h5通过jquery和qrcode在线生成二维码 首先我们需要下载一个qrcode.js文件,然后依次引入jquery和qrcode文件. 1.创建一个输入框以便做演示使用: <input id ...

  4. ubuntu用不了root用户:~$ su - root Password: su: Authentication failure怎么办?

    解除root锁定,为root用户设置密码. 打开终端输入:sudo passwd Password: <--- 输入你当前用户的密码 Enter new UNIX password: <- ...

  5. php调试之路

    解析php中die(),exit(),return的区别 die()停止程序运行,输出内容exit是停止程序运行,不输出内容return是返回值die是遇到错误才停止exit是直接停止,并且不运行后续 ...

  6. 性能调优之SQL优化

    poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821478,咨询电话010-845052 ...

  7. 3.Java集合总结系列:Set接口及其实现

    一.Set接口 Set 接口与 List 接口相比没有那么多操作方法,比如: 1.List 接口能直接设置或获取某个元素的值,而Set接口不能. 2.List 接口能直接在指定位置删除.增加元素,而S ...

  8. Maven工程webinfo下面的JSP页面无法加载.js、.css文件的解决方案

    --下面是我的工程路径 --我jsp的写法 -----启动工程,访问js文件的路径是这样的, href="http://localhost:8080/activiti/css/public. ...

  9. (转)混乱的First、Follow、Firstvt和Lastvt

    转自: http://dongtq2010.blog.163.com/blog/static/1750224812011520113332714/ 学编译原理的时候,印象最深的莫过于这四个集合了,而且 ...

  10. 因为文件组 'PRIMARY' 已满 解决办法

    简介:文件组 'PRIMARY' 已满 一般虚拟主机提供商是通过限制数据库文件的大小来实现提供定制的数据库空间的.当你把从虚拟数据库空间备份下来的文件恢复到自己的服务器上时,这个限制还是存在的.找到数 ...