我爱编程Java进阶之路

HotSpot VM

2018-04-02  本文已影响0人  zycisbg

根据阅读 《深入理解java虚拟机》 加上一点自己的理解。

JVM运行时数据区域

对象

垃圾收集器与内存分配策略

说到GC,可以从三个地方来地方来思考。哪些内存需要回收?什么时候回收?如何回收?

public class ReferenceObj {

    public Object instance = null;

    private static final int _1MB = 1024 * 1024;

    private byte[] bigSize = new byte[2 * _1MB];


}
     /**
     * 在运行这个单元测试的时候  添加参数 -XX:+PrintGCDetails  可以查看GC日志
     * 可以看到这样也是被回收的。
     */
    @Test
    public void testGC(){
        ReferenceObj objA = new ReferenceObj();
        ReferenceObj objB = new ReferenceObj();

        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        System.gc();
    }

HotSpot使用的是可达性分析算法:
这个算法的基本思想是通过一些“GC Roots”的对象做为起点,从这些节点开始向下搜索,搜索所过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象时不可用的。

GC Roots的对象包括:虚拟机栈(栈帧中的本地变量表)中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,native方法引用的对象

在JDK1.2之后,java 对引用的概念进行了扩充,将引用分为强引用,软引用,弱引用,虚引用 四种

软引用、弱引用和虚引用处理
四种引用类型的概念
这两个链接对四种引用类型讲解的非常好。

3,标记-整理(Mark-Compact)算法:复制算法在对象生存率较高时就要进行较多的复制操作,效率就会遍低,所以在老年代不适用这种算法,根据老年代的特点,推出了标记-整理算法。他和标记-清除的区别在于标记完不直接进行清除,而是让所有存活的对象向一边移动,然后直接清理掉边界以外的内存, 标记-整理算法.png

上边说完了对象存活判定的算法和垃圾收集的算法。HotSpotVM实现这些算法时必须需对算法的执行效率有严格的考量,才能保证虚拟机的高效运行。
4,枚举根节点
在运行这些算法的时候必须在一个能确保一致性的快照中进行。
也就是GC的时候必须停顿所有java执行的线程,(SUN将这件事情成为“Stop The Word”)
详细73页。

1,对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够的空间进行分配时,虚拟机将发生一次MinorGC(发生在新生代的GC,因为java对象大多都具备快速死亡的特点,所以MinorGC非常频繁,一般回收速度也比较快)

下边代码 分配3个2M的对象和一个4M的对象,当创建3个2M对象后,第四个4M对象无法放入新生代,所以进行了一次MinorGC,这次GC是Eden+survivor1区拷贝到survivor2区,因为survivor2区的大小只有1M,所以通过分配担保(JDK1.6后默认开启)把6M的对象分配给了老年代,第四个4M对象又存进了 新生代。

    private static final int _1M = 1024*1024;
    /**
     *JVM参数 :-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 
     * @param args
     */
    public static void main(String[] args) {
        byte[] allocation1,allocation2,allocation3,allocation4;
        allocation1 = new byte[2 * _1M];
        allocation2 = new byte[2 * _1M];
        allocation3 = new byte[2 * _1M];
        System.out.println("在这里进行了GC");
        allocation4 = new byte[4 * _1M];
    }

2,大对象直接存入老年代
如果有大对象存入新生代。就会导致在Eden区及两个Survivor区发生大量的复制,甚至通过内存担保复制到老年代,所以虚拟机提供了一个-XX:pretenureSizeThreshold 参数,令大于这个值的对象直接存放到老年代。如果有大量的短命大对象,是很影响JVM效率的。
3,长期存活的对象将进入老年代
通过上边两点,知道担保分配和大对象进入老年代,还有一种情况会进入老年代。
虚拟机给每个对象定义了一个年龄(Age)计数器。如果对象在Eden出生并经过一次MinorGC后仍然存活,并且能被Survivor容纳的话,则增加一岁,当她的年龄达到了15岁(或者通过-XX:MaxTenuringThreashold 设置)他就会晋升到老年代。
4,动态对象年龄判定
为了能更好的适应不同程序的内存情况,如果Survivor空间中相同年龄所有对象大小的综合大于Survivor空间的一半,年龄大于或者等于该年龄的对象也可以进入老年代,无需等到MaxTenuringThreshold 中要求的年龄.

虚拟机性能监控与故障处理工具


Attaching to process ID 12143, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.0-b11
Number of objects pending for finalization: 0 (等候回收的对象为0个)

-heap :打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况.


using parallel threads in the new generation.  ##新生代采用的是并行线程处理方式
using thread-local object allocation.   
Concurrent Mark-Sweep GC   ##同步并行垃圾回收
 
Heap Configuration:  ##堆配置情况
   MinHeapFreeRatio = 40 ##最小堆使用比例
   MaxHeapFreeRatio = 70 ##最大堆可用比例
   MaxHeapSize      = 2147483648 (2048.0MB) ##最大堆空间大小
   NewSize          = 268435456 (256.0MB) ##新生代分配大小
   MaxNewSize       = 268435456 (256.0MB) ##最大可新生代分配大小
   OldSize          = 5439488 (5.1875MB) ##老生代大小
   NewRatio         = 2  ##新生代比例
   SurvivorRatio    = 8 ##新生代与suvivor的比例
   PermSize         = 134217728 (128.0MB) ##perm区大小
   MaxPermSize      = 134217728 (128.0MB) ##最大可分配perm区大小
 
Heap Usage: ##堆使用情况
New Generation (Eden + 1 Survivor Space):  ##新生代(伊甸区 + survior空间)
   capacity = 241631232 (230.4375MB)  ##伊甸区容量
   used     = 77776272 (74.17323303222656MB) ##已经使用大小
   free     = 163854960 (156.26426696777344MB) ##剩余容量
   32.188004570534986% used ##使用比例
Eden Space:  ##伊甸区
   capacity = 214827008 (204.875MB) ##伊甸区容量
   used     = 74442288 (70.99369812011719MB) ##伊甸区使用
   free     = 140384720 (133.8813018798828MB) ##伊甸区当前剩余容量
   34.65220164496263% used ##伊甸区使用情况
From Space: ##survior1区
   capacity = 26804224 (25.5625MB) ##survior1区容量
   used     = 3333984 (3.179534912109375MB) ##surviror1区已使用情况
   free     = 23470240 (22.382965087890625MB) ##surviror1区剩余容量
   12.43827838477995% used ##survior1区使用比例
To Space: ##survior2 区
   capacity = 26804224 (25.5625MB) ##survior2区容量
   used     = 0 (0.0MB) ##survior2区已使用情况
   free     = 26804224 (25.5625MB) ##survior2区剩余容量
   0.0% used ## survior2区使用比例
concurrent mark-sweep generation: ##老生代使用情况
   capacity = 1879048192 (1792.0MB) ##老生代容量
   used     = 30847928 (29.41887664794922MB) ##老生代已使用容量
   free     = 1848200264 (1762.5811233520508MB) ##老生代剩余容量
   1.6416783843721663% used ##老生代使用比例
Perm Generation: ##perm区使用情况
   capacity = 134217728 (128.0MB) ##perm区容量
   used     = 47303016 (45.111671447753906MB) ##perm区已使用容量
   free     = 86914712 (82.8883285522461MB) ##perm区剩余容量
   35.24349331855774% used ##perm区使用比例

-histo[:live] :打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量.
-permstat :打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来.
-F :强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效.
jmap命令详解

虚拟机类加载机制

除上述情况外,都不会进行初始化,下边有三个被动引用(不会触发初始化)的例子
1,子类调用父类的静态字段(不是常量),不会对子类进行初始化
2,通过数组定义引用类,不会触发对此类的初始化

Person[] persons = new Personp[10];

3,调用常量,也不会对该类进行初始化。

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义,虚拟机规范未规定此区域的具体数据结构。然后在内存中实例化一个java.lang.Class类的对象,这个对象将作为程序访问方法区中的这些数据类型的外部接口。

加载阶段与连接阶段的部分内容,是交叉进行的。加载阶段尚未完成,连接阶段可能已经开始。

1,类与类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,
比较两个类是否相等,只有在这两个类是由同一个类加载器加载出来的才有意义。
2,双亲委派模型
从java虚拟机的角度来讲,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader)这个类由c++实现,是虚拟机一部分,另一种就是所有其他的类加载器,这些加载器由java实现,独立于虚拟机外部。并且全部继承于java.lang.ClassLoader。从开发者角度来看,可以用到常见的3种系统提供的类加载器
①启动类加载器
这个类加载器负责将存放在lib目录中的,并且是虚拟机识别的类加载到虚拟机内存中,启动类加载器无法被java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派个引导类加载器,那么直接用null代替。
②扩展类加载器
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载lib/ext目录下的,或者被java.ext.dirs系统变量所指定的路径中所有的类库,开发者可以直接使用扩展类加载器。
③应用程序加载器
这个类加载器由sun,misc.Launcher$AppClassLoader实现,由于这个类加载器时ClassLoader中的getSysClassLoader()方法的返回值,所以一般也成为系统类加载器,他负责加载用户类路径上所指定的类库,开发者也可以直接使用这个类加载器,如果应用程序中没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器。


类加载器双亲委派模型.png

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。
双亲委派模型的工作过程:如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求给父类去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器无法完成加载,子加载器才会尝试自己去完成。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如 Object类,它存放在rt.jar中,无论哪一个类加载器要加载这个类,都会委派给最顶端的启动类加载器去加载。因此Object类在程序中永远都是同一个类。
如果没有使用双亲委派。并且放在classpath中,那系统会出现不同的Object类,应用程序就会变乱。

//双亲委派模型实现
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //如果有父类,让父类去加载。
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                    //如果父类抛出异常,让子类去加载
                }
              
                if (c == null) {
                    //子类加载
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

,,,,,,,,,,,,,,,,,,,,,,,

上一篇 下一篇

猜你喜欢

热点阅读