JVM总结

1.JVM内存模型

1.本地方法栈(Native)   调用c部分

2.程序计数器  (程序代码行数)  JVM支持多个线程同时运行,每个线程都有自己的程序计数器。倘若当前执行的是 JVM 的方法,则该寄存器中保存当前执行指令的地址;倘若执行的是native 方法,则PC寄存器中为空。

3.栈 stack :每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息。栈的大小可以固定也可以动态扩展。当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误

4.堆  heap(存实际对象) 堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError

5方法区  :主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。 关于方法区内存溢出的问题会在下文中详细探讨

2.ClassLoader双亲委派机制 可以保护jvm自己的安全

   双亲委派机制 : 一层一层的让父类去加载,如果顶层的加载器不能加载,然后再向下类推

 // BootStrap (最顶层) 01 java.lang.String rt.jar   /jdk/jre/lib/ 下面的jar

 // ExtClassLoader 02     /jdk/jre/lib/ext/下面的jar

  // AppClassLoader 03   //AppClassLoader

     // 双亲委派机制 可以保护java的核心类不会被自己定义的类所替代

// -XX:+TraceClassLoading
public static void main(String[] args) {
        Object o = new Object(); // jdk 自带的
        Demo01 demo01 = new Demo01();  // 实例化一个自己定义的对象
        // null 在这里并不代表没有,只是Java触及不到!Bootstrap
        System.out.println(o.getClass().getClassLoader()); // null
        System.out.println(demo01.getClass().getClassLoader()); // AppClassLoader
        System.out.println(demo01.getClass().getClassLoader().getParent()); // ExtClassLoader
        System.out.println(demo01.getClass().getClassLoader().getParent().getParent()); // null
    }

3.JDK1.8之后和1.7区别

JDK1.7之前 
永久代:用于存储一些虚拟机加载类信息,常量,字符串、静态变量等等。。。。这些东西都会放到永久代中; 
永久代大小空间是有限的:如果满了 OutOfMemoryError:PermGen 
JDK1.8之后 
彻底将永久代移除 HotSpot jvm ,Java Heap 中或者 Metaspcace(Native Heap)元空间; 
元空间就是方法区在 HotSpot jvm 的实现; 
方法区重要就是来存:类信息,常量,字符串、静态变量、符号引用、方法代码。。。。。。 
元空间和永久代,都是对JVM规范中方法区的实现。 
元空间和永久代最大的区别:==元空间并不在Java虚拟机中,使用的是本地内存!== 
-XX:MetasapceSize10m 

4.方法区渊源

Method Area 方法区 是 Java虚拟机规范中定义的运行是数据区域之一,和堆(heap)一样可以在线程之间共享!栈不能共享

天上飞的理念都会有落地的实现!

JDK1.7之前

永久代:用于存储一些虚拟机加载类信息,常量,字符串、静态变量等等。。。。这些东西都会放到永久代中;

永久代大小空间是有限的:如果满了 OutOfMemoryError:PermGen

JDK1.8之后

彻底将永久代移除 HotSpot jvm ,Java Heap 中或者 Metaspcace(Native Heap)元空间;

元空间就是方法区在 HotSpot jvm 的实现;

方法区重要就是来存:类信息,常量,字符串、静态变量、符号引用、方法代码。。。。。。

元空间和永久代,都是对JVM规范中方法区的实现。

元空间和永久代最大的区别:==元空间并不在Java虚拟机中,使用的是本地内存!==

-XX:MetasapceSize10m

5.堆内存和GC过程

Java7之前: 

Heap 堆,一个JVM实例中只存在一个堆,堆的内存大小是可以调节的。 
可以存的内容:类、方法、常量、保存了类型引用的真实信息; 
分为三个部分: 
新生区:Young (Eden-s0-s1)  Eden-s0-s1默认8:1:1
养老区:Old Tenure   大对象(-XX:PretenureSizeThreadshold=1024)直接进入老年代,在新生代里面长期存活的对象进入老年代。老年代对应的是major gc。

新生区和养老区默认1:2
永久区:Perm 
堆内存在==逻辑上==分为三个部分:新生、养老、永久(JDK1.8以后,叫元空间) 
物理上只有 新生、养老;元空间在本地内存中,不在JVM中! 
GC 垃圾回收主要是在 新生区和养老区,又分为 普通的GC 和 Full GC,如果堆满了,就会爆出 OutOfMemory; 
新生区 
新生区 就是一个类诞生、成长、消亡的地方! 
新生区细分: Eden、s(from to),所有的类Eden被 new 出来的,慢慢的当 Eden 满了,程序还需要创建对象的时候,就会触发一次轻量级GC;清理完一次垃圾之后,会将活下来的对象,会放入幸存者区(),....... 清理了 20次之后,出现了一些极其顽强的对象,有些对象突破了15次的垃圾回收!这时候就会将这个对象送入养老区!运行了几个月之后,养老区满了,就会触发一次 Full GC;假设项目1年后,整个空间彻彻底底的满了,突然有一天系统 OOM,排除OOM问题,或者重启; 
Sun HotSpot 虚拟机中,内存管理(分代管理机制:不同的区域使用不同的算法!) 
Eden from to 
99% 的对象在 Eden 都是临时对象; 
养老区 
15次都幸存下来的对象进入养老区,养老区满了之后,触发 Full GC 
默认是15次,可以修改! 
永久区(Perm) 
放一些 JDK 自身携带的 Class、Interface的元数据; 
几乎不会被垃圾回收的; 
OutOfMemoryError:PermGen 在项目启动的时候永久代不够用了?加载大量的第三方包! 
JDK1.6之前: 有永久代、常量池在方法区; 
JDK1.7:有永久代、但是开始尝试去永久代,常量池在堆中; 
JDK1.8 之后:永久代没有了,取而代之的是元空间;常量池在元空间中; 
闲聊:方法区和堆一样,是共享的区域,是JVM 规范中的一个逻辑的部分,但是记住它的别名 非堆 
元空间:它是本地内存! 

6.堆内存调优和常用参数(初识)

1.8之后permSize和MaxPermSize被-XX:MetaspaceSize和-XX:MaxMetaspaceSize取代

-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
 -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

 

-XX:+PrintGCDetails:输出详细的GC处理日志

-XX:+PrintGCTimeStamps:输出GC的时间戳信息

-XX:+PrintGCDateStamps:输出GC的时间戳信息(以日期的形式)

-XX:+PrintHeapAtGC:在GC进行处理的前后打印堆内存信息

-Xloggc:(SavePath):设置日志信息保存文件

Minor GC ,Full GC 触发条件

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法去空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

Java堆:所有线程共享。虚拟机启动时创建。存放对象实力和数组。所占内存最大。分为新生代(Young区),老年代(Old区)。新生代分Eden区,Servior区。Servior区又分为From space区和To Space区。Eden区和Servior区的内存比为8:1。 当扩展内存大于可用内存,抛OOM。

 

GC流程  ,对象如何晋升到老年代

对象优先在新生代区中分配,若没有足够空间,Minor GC; 大对象(需要大量连续内存空间)直接进入老年态;长期存活的对象进入老年态。如果对象在新生代出生并经过第一次MGC后仍然存活,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。

 

  1. 【对象优先在Eden中分配,当Eden中没有足够的空间分配时会促发一次Minor GC。每次Minor GC结束后,Eden区会清空,因为它会把Eden中还依然存活的对象放到Survivor中,当Survivor中放不下时,则由分派担保进入老年代中。
  2. 大对象直接进入老年代中。-XX:+PretenuerSizeThreshold 控制”大对象的“的大小。即当创建的对象大于这个临界值时,则该对象直接进入老年代。
  3. 长期存活的对象将进入老年代。虚拟机对每个对象定义了一个对象年龄(Age)计数器。当年龄增加到一定的临界值时,就会晋升到老年代中,该临界值由参数:-XX:MaxTenuringThreshold来设置。如果对象在Eden出生并在第一次发生Minor GC时仍然存活,并且能够被Survivor中所容纳的话,则该对象会被移动到Survivor中,并且设Age=1;以后每经历一次Minor GC,该对象还存活的话会被移动到另一个Survivor区中,并且Age=Age+1。
  4. 动态对象年龄判定:如上所示,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor区中相同年龄(设年龄为age)的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄(age)的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。】

老年代的垃圾回收称作Full GC。老年代所占用的内存大小为-Xmx对应的值减去-Xmn对应的值。

/**
 * 默认情况:
 * maxMemory : 1808.0MB (虚拟机试图使用的最大的内存量  一般是物理内存的 1/4)
 * totalMemory : 123.0MB (虚拟机试图默认的内存总量 一般是物理内存的 1/64)
 */
// 我们可以自定堆内存的总量
// -XX:+PrintGCDetails; // 输出详细的垃圾回收信息
// -Xmx: 最大分配内存; 1/4
// -Xms: 初始分配的内存大小; 1/64

// -Xmx1024m -Xms1024m -XX:+PrintGCDetails
public class Demo01 {
    public static void main(String[] args) {
        // 获取堆内存的初始大小和最大大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();

        System.out.println("maxMemory="+maxMemory+"(字节)、"+(maxMemory/1024/(double)1024)+"MB");
        System.out.println("totalMemory="+totalMemory+"(字节)、"+(totalMemory/1024/(double)1024)+"MB");

    }
}

GCDump分析内存泄露分析

安装JProfile

运行时加上参数:-XX:+HeapDumpOnOutOfMemoryError   生成的文件在项目根目录下/同级

你可能感兴趣的:(java)