您的位置:首页 > 编程语言 > Java开发

浅谈Java虚拟机

2017-12-28 10:40 169 查看

JVM 组成

我们知道JVM有五大区块, Method Area(方法区)、VMStack(java堆)、VM Heap(java栈)、Program counter Register( 程序计数器)、Native Method Area (本地方法栈)如图-1

                                                                                                                JVM区块图

 


                                                                                                        图-1

                                                                                        JVM区块字典

区块

方法区

Java堆

Java栈

程序计数器

本地方法栈

共享

线程共享

线程共享

非线程共享

非线程同享

非线程同享

存储

类的信息

对象

局部变量

指令的地址

本地局部变量

垃圾收回

Jdk7支持

全支持

不支持

不支持

不支持

                                                                                            表-1

JVM共享:

    共享有线程共享与非线程共享,一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。

    概括地说来,JVM初始运行的时候都会同时分配好Method Area(方法区) 和Heap(堆),而JVM每遇到一个线程,就再为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈),当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。

    非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。
http://blog.csdn.net/qq_26716941/article/details/51747276

JVM存储:

方法区:方法区用来存放类型信息、常量池、字段信息、方法信息、静态变量、ClassLoader引用、类Class的引用、方法表;
http://blog.csdn.net/zero__007/article/details/51043832
java堆:存放对象和数组以及对象的实例变量(即全局变量)。

java栈:存放方法中的局部变量(成员变量在堆区),以及对象的引用。
http://blog.csdn.net/regan1994/article/details/40949901
程序计数器:线程独享,记录下一条指令的所在存储单元的地址。

本地方法栈:与java栈类似,存储的是native局部变量。

JVM垃圾收回发展史

    在JDK1.2之前,使用的是引用计数器算法,即当这个类被加载到内存以后,就会产生方法区,堆栈、程序计数器等一系列信息,当创建对象的时候,为这个对象在堆栈空间中分配对象,同时会产生一个引用计数器,同时引用计数器+1,当有新的引用的时候,引用计数器继续+1,而当其中一个引用销毁的时候,引用计数器-1,当引用计数器被减为零的时候,标志着这个对象已经没有引用了,可以回收了!这种算法在JDK1.2之前的版本被广泛使用,但是随着业务的发展,很快出现了一个问题

当我们的代码出现下面的情形时,该算法将无法适应
          a)         ObjA.obj = ObjB

           b)         ObjB.obj - ObjA

                 这样的代码会产生如下引用情形 objA指向objB,而objB又指向objA,这样当其他所有的引用都消失了之后,objA和objB还有一个相互的引用,也就是说两个对象的引用计数器各为1,而实际上这两个对象都已经没有额外的引用,已经是垃圾了。



2、              根搜索算法

                   根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。



目前java中可作为GC Root的对象有
1、    虚拟机栈中引用的对象(本地变量表)

2、    方法区中静态属性引用的对象

3、    方法区中常量引用的对象

4、    本地方法栈中引用的对象(Native对象)

说了这么多,其实我们可以看到,所有的垃圾回收机制都是和引用相关的,那我们来具体的来看一下引用的分类,到底有哪些类型的引用?每种引用都是做什么的呢?

Java中存在四种引用,每种引用如下:
1、  强引用

只要引用存在,垃圾回收器永远不会回收

Object obj = new Object();

//可直接通过obj取得对应的对象 如obj.equels(new
Object());

而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。
2、  软引用

非必须引用,内存溢出之前进行回收,可以通过以下代码实现

Object obj = new Object();

SoftReference<Object> sf = new SoftReference<Object>(obj);

obj = null;

sf.get();//有时候会返回null

这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。
3、  弱引用

第二次垃圾回收时回收,可以通过如下代码实现

Object obj = new Object();

WeakReference<Object> wf = new WeakReference<Object>(obj);

obj = null;

wf.get();//有时候会返回null

wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾

弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。

弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器
4、  虚引用(幽灵/幻影引用)

           垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现

Object obj = new Object();

PhantomReference<Object> pf = new PhantomReference<Object>(obj);

obj=null;

pf.get();//永远返回null

pf.isEnQueued();//返回从内存中已经删除

虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。

虚引用主要用于检测对象是否已经从内存中删除。

在上文中已经提到了,我们的对象在内存中会被划分为5块区域,而每块数据的回收比例是不同的,根据IBM的统计,数据如下图所示:


       我们知道,方法区主要存放类与类之间关系的数据,而这部分数据被加载到内存之后,基本上是不会发生变更的,Java堆中的数据基本上是朝生夕死的,我们用完之后要马上回收的,而Java栈和本地方法栈中的数据,因为有后进先出的原则,当我取下面的数据之前,必须要把栈顶的元素出栈,因此回收率可认为是100%;而程序计数器我们前面也已经提到,主要用户记录线程执行的行号等一些信息,这块区域也是被认为是唯一一块不会内存溢出的区域。在SunHostSpot的虚拟机中,对于程序计数器是不回收的,而方法区的数据因为回收率非常小,而成本又比较高,一般认为是“性价比”非常差的,所以Sun自己的虚拟机HotSpot中是不回收的!但是在现在高性能分布式J2EE的系统中,我们大量用到了反射、动态代理、CGLIB、JSP和OSGI等,这些类频繁的调用自定义类加载器,都需要动态的加载和卸载了,以保证永久带不会溢出,他们通过自定义的类加载器进行了各项操作,因此在实际的应用开发中,类也是被经常加载和卸载的,方法区也是会被回收的!但是方法区的回收条件非常苛刻,只有同时满足以下三个条件才会被回收!

 

1、所有实例被回收

2、加载该类的ClassLoader被回收

3、Class对象无法通过任何途径访问(包括反射)

Java1.2之前主要通过引用计数器来标记是否需要垃圾回收。

java1.2之后都使用根搜索算法来收集垃圾,而收集后的垃圾是通过一下算法来回收的。

1、    标记-清除算法

2、    复制算法

3、    标记-整理算法

JVM垃圾清楚算法

Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。

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

第一步标记出要清除的对象,第二步执行清除内存动作,优点:实现简单,缺点:标记过程需要扫描整个内存,效率低,清除垃圾对象后会产生大量的碎片,不利于大对象分配和对象访问。

2、标记-复制算法(分代堆存储虚拟机中新生代就是使用这个回收算法)

将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,优点:实现简单,运行高效。缺点:可使用的内存降为原来一半。

3、标记-整理算法:

标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。

标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。

复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。

4、分代收集算法:

根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

现代的Java虚拟机就联合使用了分代复制、标记-清除和标记-整理算法,java虚拟机垃圾收集器关注的内存结构如下:

JVM 四种垃圾收集器

Java有四种类型的垃圾回收器:

串行垃圾回收器(Serial GarbageCollector)

并行垃圾回收器(Parallel GarbageCollector)

并发标记扫描垃圾回收器(CMS GarbageCollector)

G1垃圾回收器(G1 Garbage Collector)

每种类型都有自己的优势与劣势。重要的是,我们编程的时候可以通过JVM选择垃圾回收器类型。我们通过向JVM传递参数进行选择。每种类型在很大程度上有所不同并且可以为我们提供完全不同的应用程序性能。理解每种类型的垃圾回收器并且根据应用程序选择进行正确的选择是非常重要的。

1、串行垃圾回收器

串行垃圾回收器通过持有应用程序所有的线程进行工作。它为单线程环境设计,只使用一个单独的线程进行垃圾回收,通过冻结所有应用程序线程进行工作,所以可能不适合服务器环境。它最适合的是简单的命令行程序。

通过JVM参数-XX:+UseSerialGC可以使用串行垃圾回收器。

2、并行垃圾回收器

并行垃圾回收器也叫做 throughputcollector 。它是JVM的默认垃圾回收器。与串行垃圾回收器不同,它使用多线程进行垃圾回收。相似的是,它也会冻结所有的应用程序线程当执行垃圾回收的时候

3、并发标记扫描垃圾回收器

并发标记垃圾回收使用多线程扫描堆内存,标记需要清理的实例并且清理被标记过的实例。并发标记垃圾回收器只会在下面两种情况持有应用程序所有线程。

    当标记的引用对象在tenured区域;

    在进行垃圾回收的时候,堆内存的数据被并发的改变。

相比并行垃圾回收器,并发标记扫描垃圾回收器使用更多的CPU来确保程序的吞吐量。如果我们可以为了更好的程序性能分配更多的CPU,那么并发标记上扫描垃圾回收器是更好的选择相比并发垃圾回收器。

通过JVM参数 XX:+USeParNewGC 打开并发标记扫描垃圾回收器。

4、G1垃圾回收器

G1垃圾回收器适用于堆内存很大的情况,他将堆内存分割成不同的区域,并且并发的对其进行垃圾回收。G1也可以在回收内存之后对剩余的堆内存空间进行压缩。并发扫描标记垃圾回收器在STW情况下压缩内存。G1垃圾回收会优先选择第一块垃圾最多的区域

通过JVM参数 –XX:+UseG1GC 使用G1垃圾回收器

Java 8 的新特性

在使用G1垃圾回收器的时候,通过 JVM参数 -XX:+UseStringDeduplication 。 我们可以通过删除重复的字符串,只保留一个char[]来优化堆内存。这个选择在Java 8 u 20被引入。

我们给出了全部的四种Java垃圾回收器,需要根据应用场景,硬件性能和吞吐量需求来决定使用哪一种。

垃圾回收的JVM配置

下面的JVM关键配置都与Java垃圾回收有关。

运行的垃圾回收器类型

配置

描述

-XX:+UseSerialGC

串行垃圾回收器

-XX:+UseParallelGC

并行垃圾回收器

-XX:+UseConcMarkSweepGC

并发标记扫描垃圾回收器

-XX:ParallelCMSThreads=

并发标记扫描垃圾回收器=为使用的线程数量

-XX:+UseG1GC

G1垃圾回收器

GC的优化配置

配置

描述

-Xms

初始化堆内存大小

-Xmx

堆内存最大值

-Xmn

新生代大小

-XX:PermSize

初始化永久代大小

-XX:MaxPermSize

永久代最大容量

使用JVM GC参数的例子

java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m-XX:+UseSerialGC -jar java-application.jar


Java GC
监视和分析工具

下面是一些可用的工具,每个都有自己的优势和缺点。我们可以通过选择正确的工具并分析,来提升应用程序的性能。这篇教程中,我们选用Java VisualVM。

Java VisualVM
Naarad
GCViewer
IBM Pattern Modeling and Analysis Tool for Java Garbage Collector
HPjmeter
IBM Monitoring and Diagnostic Tools for Java-Garbage Collection and Memory
Visualizer
Verbose GC Analyzer

Java VisualVM

Java VisualVM使用是免费的,其需要安装Java SESDK。看一下Java JDK的bin文件夹中(路径:\Java\jdk1.8.0\bin),这里面有很多javac和java工具,jvisualvm就是其中之一。

Java VisualVM能够被用于:

  生成并分析堆的内存转储;
  在MBeans上观察并操作;
  监视垃圾回收;
  内存和CPU性能分析;

1、启动VisualVM

jvisualvm位于JDK bin文件夹下,直接点击就可以。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: