JVM第一弹

基本概念

JVM是可运行java代码的假想计算机,包括一套字节码指令集,一组寄存器,一个栈,一个垃圾回收、堆和一个存储方法域。JVM是运行在操作系统之上的,它与硬件没有直接的交互。

运行过程

我们都知道Java代码源文件,通过编译器能够产生相应的.Class字节码文件,而字节码文件又通过Java虚拟机中的解释器,编译成特定机器上的机器码。

① Java源文件 ——> 编译器 ——> 字节码文件

② 字节码文件 ——> JVM ——> 机器码

每种平台的解释器是不同的,但是虚拟机是相同的,这也就是java为什么能够跨平台的原因了。当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序 启动就会存在多个虚拟机实例。

程序退出或者关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不能共享。

类加载器

什么是类的加载?

类的加载是指将类的字节码文件数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。

类的加载的最终产品是位于堆区内中的Class对象,Class对象封装了类在方法区内的数据结构,并且向java程序员提供了访问方法区内的数据结构的接口。

类加载器包括:

  1. 启动类加载器(BootStrap)

    ——主要有C++进行实现的。用来加载jdk安装目录下的:jre/lib下的可执行jar包。

    也可以通过设置 -XbootClasspath来动态指定jar包位置。在java代码中无法获取到该对象。

String str = new String("HelloWorld");
System.out.println(str.getClass().getClassLoader()); //控制台打印null
  1. 扩展类加载器(ExtClassLoader)

    ——是java代码实现的,用来加载java安装目录下 jre/lib/ext 目录中的可执行jar包。

  2. 应用程序类加载器(AppClassLoader)

    ——是java代码实现的,用来加载用户编写的代码。我们新建一个类,获取其类加载器就是AppClassLoader


public class MyClassLoaderTest { public static void main(String[] args) {
String str = new String("HelloWorld");
// 打印null
System.out.println(str.getClass().getClassLoader()); // 打印sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(MyClassLoaderTest.class.getClassLoader());
// 打印sun.misc.Launcher$ExtClassLoader@4554617c
System.out.println(MyClassLoaderTest.class.getClassLoader().getParent());
// 打印null
System.out.println(MyClassLoaderTest.class.getClassLoader().getParent().getParent()); } }

由上述代码可见: AppClassLoader extend ExtClassLoader extend BootstrapClassLoader

  1. 用户自定义类加载器

    —— 用户编写类继承自 java.lang.ClassLoader

为了防止用户自定义类与jdk自带的类冲突,jdk内有双亲委派机制和沙箱机制。

双亲委派机制

上述过程中,我们认识到了类加载器之间的继承关系。当java在加载类的时候,由AppClassLoader委派其父类ExtClassLoader进行加载,ExtClassLoader会再次委派其父类BootStrapClassLoader进行加载,

如果BootStrapClassLoader找到该类那么加载该类返回该类的Class对象,但是,如果此时BootStrapClassLoader没有找到该类,

那么就需要ExtClassLoader自身进行加载,如果ExtClassLoader找到该类那么加载该类返回该类的Class对象,

但是,如果ExtClassLoader也没有找到该类,那么就要由AppClassLoader进行加载。

如果最后AppClassLoader也没有找到该类,那么就会抛出 ClassNotFoundException

类加载器没有向下寻找,没有getChild只有getParent

如果你自己定义了一个与jdk自带类名包名一致的类,那么java也不会去加载该类。

JVM结构

JVM内存区域主要分为

  • 线程私有区域
  1. 程序计数器
  2. 虚拟机栈
  3. 本地方法区
  • 线程共享区域
  1. Java堆
  2. 方法区
  • 直接内存

生命周期

  1. 线程私有数据区域生命周期与线程相同,依赖用户线程的启动/结束而创建/销毁。
  2. 线程共享区域随着虚拟机的启动/关闭 而 创建/销毁。

方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序计数器是运行时线程私有的内存区域。

  • 方法区

    主要存放静态变量,常量,Class类模板(接口定义,构造函数),运行时常量池。

  • java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。又被称作为运行时数据区。

  • 程序计数器(Program Counter Register),是一块比较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。每个线程都有一个私有的,可以理解为它是一个指针,指向方法字节码地址,用来标记下一个要执行的方法字节码地址。

  • JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同,线程结束栈内存也就释放了,对于栈来说不存在来及回收的问题。主要保存八大基本数据类型的变量、对象的引用变量以及实例方法。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

  • 本地方法栈(Native Method Stacks),与c/c++交互的一块区域,本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

垃圾回收器

  • Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。
  • ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。
  • Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。
  • Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
  • CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
  • G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征

调优命令

Sun JDK监控和故障处理命令有 jps jstat jmap jhat jstack jinfo

  • jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。
  • jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
  • jmap,JVM Memory Map命令用于生成heap dump文件
  • jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
  • jstack,用于生成java虚拟机当前时刻的线程快照。
  • jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。

调优工具

常用调优工具分为两类

  1. jdk自带监控工具:jconsole和jvisualvm
  • jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控
  • jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。
  1. 第三方有:MAT(Memory Analyzer Tool)、GChisto。
  • MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
  • GChisto,一款专业分析gc日志的工具

你知道哪些JVM性能调优

  • 设定堆内存大小

    -Xmx:堆内存最大限制。

  • 设定新生代大小。

    新生代不宜太小,否则会有大量对象涌入老年代

    -XX:NewSize:新生代大小

    -XX:NewRatio 新生代和老生代占比

    -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比

  • 设定垃圾回收器

    年轻代用 -XX:+UseParNewGC

    年老代用-XX:+UseConcMarkSweepGC

什么时候出现栈溢出

递归操作,程序没有出口会一直进行压栈操作

为什么会出现栈溢出

栈的深度不够了

堆内存

逻辑上分为

  • 新生区
  • 养老区
  • 永久区

物理上分为

新生区 、 养老区、 永久区

又将新生区分为了三个区

  • 伊甸园区(80%)
  • 幸存者from区(10%)
  • 幸存者to区(10%)

新new的对象都放在伊甸园区,存活率2%,其他对象都被垃圾回收器回收

没有被垃圾回收幸存下来的对象将会保存到幸存者区

当伊甸园区内存不足时,会进行轻量级(minor GC)垃圾回收,将幸存者from区和伊甸园区的还在用的对象移动到幸存者to区,

然后清空幸存者from区和伊甸园区,幸存者from区清空之后会交换from区和to区,保证to区始终是空的。注意from区向to区移动之前会判断对象的年龄,

如果大于15,直接移动到养老区。年龄计数的原理:垃圾回收器回收一次,幸存活一次加一岁。

如果养老区的内存也不够用了,就会触动重量级GC(full GC)将养老区和新生区全量级回收垃圾对象。如果FullGC之后养老区的内存还是不够用,那么会引发OOM。

如果程序一开始就new了一个比伊甸园区大的对象,伊甸园区没有足够的空间存放应该如何存放呢?此时会将对象存放到养老区,如果养老区也不够存储,那么会引发OOM。

对象分配规则

对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。

大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。

长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。

动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。

空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。

产生OOM的原因?

  • java设置的堆内存不够,可以通过设置 -Xms -Xmx 来调整堆内存的大小
  • java内存中创建了大量的大对象,并且长时间不能被垃圾回收器回收

java8与元数据

在java8中,永久代已经移除了,被“元数据”(元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代的最大区别在于:

元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

类的源数据放入本定内存中,字符串和类的静态变量放到java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

垃圾回收与算法

如果确定垃圾

  1. 引用计数法

    在java中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,一个简单的方法就是通过引用计数来判断一个对象是否可以回收。简单来说,即一个对象如果没有任何与之关联的引用,即他们的引用计数都不为0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。

  2. 可达性分析

    为了解决引用计数法的循环引用问题,java使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索,如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。

注意 不可达并不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍是可回收对象,则将面临回收。

  1. 标记清楚算法

最基础的垃圾回收算法,分为两个阶段:标记清楚。标记阶段是标记出来所有要回收的对象,清楚阶段回收被标记的对象所占的空间。

该算法的缺点:

内存碎片化严重,垃圾清理完成后,造成很多内存空间不连续。后续可能发生大对象不能找到可利用的问题。

MajorGC使用该算法

  1. 复制算法

    为了解决标记清楚算法内存碎片化的缺陷而提出的算法。按照内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清理掉。

MinorGC使用该算法

缺点:

这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可以用内存被压缩到了原本的一半。且存活对象增多的话,copying算法的效率也大大降低。

  1. 标记整理算法

    结合以上两个算法,为了避免缺陷而提出。标记阶段和标记清楚算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清楚端边界的对象.

  1. 分代收集算法

    分代收集算法是目前大部分JVM所采用的方法,其核心思想是根据对象村花的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为老生代和新生代。老生代的特点是每次垃圾回收只有少量对象需要被回收,新生代的特点是每次垃圾回收是都有大量垃圾需要被回收,因此可以根据不同区域采用不同的算法。

6.1. 新生代与复制算法

目前大部分的JVM的GC对于新生代都采取了copying方法,因为新生代中每次垃圾回收都要回收大部分对象,

即要复制的操作比较少,但通常并不是按照1:1来划分新生代。一般将新生代划分为一块较大的Eden空间和两个比较小的Surviror空间(FromSpace,ToSpace),每次使用Eden空间和其中的一块Surivor空间,当进行回收时,将该两块空间中还存活的对象复制到另外一块Survivor空间中。

6.2 老年代与标记复制算法

而老年代因为每次只回收少量的对象,因此采用Mark-Compact算法。

  1. JAVA虚拟机提到过的处于方法区的永生带,它用来存储class类,常量、方法描述等。对永生代的回收主要包括废弃常量和无用的类

  2. 对象的内存分配主要在新生代的EdenSpace和SurvivorSpace的FormSpace(Survivor目前存放对象的那一块),少数情况会直接分配到老生代。

  3. 当新生代的EdenSpace和FromSpace空间不足时就会发生一次GC,进行GC后,EdenSpace和FromSpace区的存活对象会被移动到ToSpace,然后将EdenSpace和FromSpace进行清理。

  4. 如果ToSpace无法足够存储某个对象,则将这个对象存储到老生代。

  5. 进行GC后,使用的便是EdenSpace和ToSpace了,如此反复循环。

  6. 当对象在Survivor区躲过一次GC后,其年龄就会+1。默认情况下年龄达到15的对象就会移动到老生代中

Java中的四种引用

强引用

在Java中最常见的就是强引用,把一个对象赋值给一个引用变量,这个引用变量就是一个强引用。

当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM也不会回收。因此强引用是造成Java内存泄漏主要原因之一。

软引用

软引用需要使用SoftReference类来实现,对于只有软引用的对象来说,当系统内存足够时他不会被回收,当系统内存足够用时,它不会被回收,当系统内存不足时它会被回收。软引用通常用在对内存敏感的程序中。

弱引用

弱引用需要用WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间足够,总会回收该对象占用的内存。

虚引用

虚引用需要PhantomReference类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。

JVM第一弹的更多相关文章

  1. Hadoop基础-MapReduce的工作原理第一弹

    Hadoop基础-MapReduce的工作原理第一弹 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在本篇博客中,我们将深入学习Hadoop中的MapReduce工作机制,这些知识 ...

  2. typecho流程原理和插件机制浅析(第一弹)

    typecho流程原理和插件机制浅析(第一弹) 兜兜 393 2014年03月28日 发布 推荐 5 推荐 收藏 24 收藏,3.5k 浏览 虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终 ...

  3. 我的长大app开发教程第一弹:Fragment布局

    在接下来的一段时间里我会发布一个相对连续的Android教程,这个教程会讲述我是如何从零开始开发“我的长大”这个Android应用. 在开始之前,我先来介绍一下“我的长大”:这是一个校园社交app,准 ...

  4. Java基础-程序流程控制第一弹(分支结构/选择结构)

    Java基础-程序流程控制第一弹(分支结构/选择结构) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.if语句 1>.if语句的第一种格式 if(条件表达式){ 语句体: ...

  5. RMQ_第一弹_Sparse Table

    title: RMQ_第一弹_Sparse Table date: 2018-09-21 21:33:45 tags: acm RMQ ST dp 数据结构 算法 categories: ACM 概述 ...

  6. codechef 营养题 第一弹

    第一弾が始まる! 定期更新しない! 来源:http://wenku.baidu.com/link?url=XOJLwfgMsZp_9nhAK15591XFRgZl7f7_x7wtZ5_3T2peHh5 ...

  7. 好玩的WPF第一弹:窗口抖动+边框阴影效果+倒计时显示文字

    原文:好玩的WPF第一弹:窗口抖动+边框阴影效果+倒计时显示文字 版权声明:转载请联系本人,感谢配合!本站地址:http://blog.csdn.net/nomasp https://blog.csd ...

  8. [Git] 002 初识 Git 与 GitHub 之加入文件 第一弹

    在 GitHub 的 UI 界面使用 Git 往仓库里加文件 第一弹 1. 点击右上方的 Create new file 2. 在左上方填入文件名,若有后缀,记得加上 3. 页面跳转,此时已有两个文件 ...

  9. [Markdown] 03 进阶语法 第一弹

    目录 1. YMAL 题头 2. 缩写 3. 强调 4. 自定义 <div> 标签 5. <cite> 标签 5. <code> 与 <br> 标签 6 ...

随机推荐

  1. 【Docker】镜像分层存储与镜像精简

    Linux操作系统 Linux操作系统由内核空间和用户空间组成. 内核空间是kernel,用户空间是rootfs, 不同Linux发行版的区别主要是rootfs.比如 Ubuntu 14.04 使用 ...

  2. 0010 CSS字体样式属性:font-size、font-family、Unicode字体、font-weight、font-style、综合设置、color、 text-align、line-height、text-indent、text-decoration、、、

    CSS字体样式属性.调试工具 目标 应用 使用css字体样式完成对字体的设置 使用css外观属性给页面元素添加样式 使用常用的emment语法 能够使用开发人员工具代码调试 1.font字体 1.1 ...

  3. 菜鸟系列Fabric源码学习 — committer记账节点

    Fabric 1.4 源码分析 committer记账节点 本文档主要介绍committer记账节点如何初始化的以及committer记账节点的功能及其实现. 1. 简介 记账节点负责验证交易和提交账 ...

  4. libcurl库的简单使用

    #include <stdio.h> #include <tchar.h> #include <windows.h> #include <process.h& ...

  5. $UVA10559\ Blocks\ $区间$dp$

    \(Des\) • 有一排数量为N的方块,每次可以把连续的相同颜色的区间消除,得到分数为 区间长度的平方,然后左右两边连在一起,问最大分数为多少. • n<=1 \(Sol\) 正解状态设得奇奇 ...

  6. fiddler 手机 https 抓包

    fiddler手机抓包原理fiddler手机抓包的原理与抓pc上的web数据一样,都是把fiddler当作代理,网络请求走fiddler,fiddler从中拦截数据,由于fiddler充当中间人的角色 ...

  7. 从零开始安装Redis 集群(Linux CenOS7)

    从零开始安装Redis 集群(Linux CenOS7) 使用ISO安装CentOS7虚拟机 配置静态IP(参考Mac VMware Fusion CentOS7配置静态IP) 安装vim [root ...

  8. linux中的ldd命令简介

    转载自:http://blog.csdn.net/stpeace/article/details/47069215 在linux中, 有些命令是大家通用的, 比如ls, rm, mv, cp等等, 这 ...

  9. TVP思享 | 四个全新维度,极限优化HTTP性能

    导语 | 当产品的用户量不断翻番时,需求会倒逼着你优化HTTP协议.那么,要想极限优化HTTP性能,应该从哪些维度出发呢?本文将由TVP陶辉老师,为大家分享四个全新维度.「TVP思享」专栏,凝结大咖思 ...

  10. BigInteger&BigDecimal类

    BigInteger类 当需要处理超过 long 数值范围的大整数时,java.math 包中的 BigInteger 类提供任意精度的整数运算. 构造方式 //构造方法,将BigInteger的十进 ...