JVM简析

  • JVM简析:

  • JVM组成结构谈谈

    • Java Virtual Machine

    • 操作系统(如Windows、Linux等)

    • 硬件体系(如Intel体系、SPAC等)

    JVM是运行在操作系统之上的,它与硬件没有直接交互

  1. Class Loader类加载器:

    负责加载class文件,class文件在开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定

  2. Native Interface

    本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,于是从内存中专门开辟了一块区域处理标记native方法,在Execution Engine执行时加载native libraies.

    目前方法使用很少,除非和硬件有关应用,或者Java系统管理生产设备,在企业级应用中已经比较少见。

    因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可使用Web Service等等。

  3. Method Area 方法区 (淘宝周志明)

    方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也 在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。

    静态变量+常量+类信息+运行时常量池存在方法区中+实例变量存在堆内存中

  4. PC Register 程序计数器

    每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。

  5. Native Method Stack 本地方法栈

    它的具体做法是 Native Method Stack 中登记native方法,在Execution Engine执行时加载native libraies.

栈管运行,堆管存储**!!!

  1. Stack 栈是什么:栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命周期是跟随线程的生命周期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就OVER,生命周期和线程一致,是线程私有的。基本类型的变量和对象的引用变量都是在函数的栈内存中分配!!!。

    1. 栈存储什么?栈帧主要保存3 类数据:

      • 本地变量(Local Variables):输入参数和输出参数以及方法内的变量;

      • 栈操作(Operand Stack):记录出栈、入栈的操作;

      • 栈帧数据(Frame Data):包括类文件、方法等。

    2. 栈运行原理:先进后出,后进先出。like 弹夹。

  • 判断JVM优化是哪里? 主要优化堆

由线程共享的数据区:只有方法区和堆

三种JVM:

  • Sun公司的 HotSpot Sun被Oracle收购

  • BEA公司的 JRockit 被Oracle收购

  • IBM公司的J9 VM

7.1 新生区

新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两个部分:

伊甸区:Eden Space;

幸存区:Survivor 1 Space;

所有的类都是在伊甸区被new出来的。

幸存区有两个:

  • 0区(Survivor 0 Space)

  • 1区(Survivor 1 Space)

当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。若养老区也满了,那么这时将产生minor GC(Full GC),进行养老区的内存清理。若养老区执行了Full GC 之后发现依然无法进行对象的保存,就会产生OOM异常"Out Of Memory Error".

如果出现java.lang.OutOfMemoryError:Java heap space异常,说明java虚拟机的堆内存不够。原因有两个:

  • 1.Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。

  • 2.代码中创建了大量的对象,并且长时间不能被垃圾收集器收集(存在被引用)

7.2 养老区(Tenure Generation Space)

养老区用于保存从新生区筛选出来的 JAVA对象,一般池对象都在这个区域活跃。

7.3 永久区(Permanent Space)

永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收的,关闭JVM才会释放此区域所占的内存。

如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个TomCat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。

JDK 1.6及之前:有永久代,常量池1.6在方法区

JDK 1.7:有永久代,但已经逐步 “去永久代”,常量池1.7在堆内存

JDK1.8及之后:无永久代,常量池1.8在元空间

程序内存划分小总结

实际而言,方法区(Method Area) 和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等待,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。

对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)”,但严格本质上说两者不同,或者说使用永久带来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。

常量池(Constant Pool) 是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,这部分内容将在类加载后进入方法区的运行时常量池中存放。

 

JVM垃圾收集(Java Garbage Collection)

上集,本次均以JDK1.7+HotSpot为例

1.JDK1.8之后将最初的永久代取消了,由元空间取代。

2.目的:将HotSpot与JRockit两个虚拟机标准

GC(堆内存调优简介)

-Xms:设置初始值分配大小,默认为物理内存的1/64

-Xmx:最大分配内存,默认为物理内存的1/4

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

package com.mvj;
​
public class StackDemo2 {
    public static void main(String[] args) {
        long maxMemory = Runtime.getRuntime().maxMemory();// 返回java
                                                            // 虚拟机试图使用的最大内存量
        long totalMemory = Runtime.getRuntime().totalMemory();// 返回java
​
        System.out.println("maxMemory=" + maxMemory + "(字节)" + (maxMemory / (double) 1024 / 1024) + "MB");
        System.out.println("TotalMemory" + totalMemory + "(字节)" + (totalMemory / (double) 1024 / 1024) + "MB");
​
    }
​
}
​

结果:

maxMemory=2731016192(字节)2604.5MB
TotalMemory185073664(字节)176.5MB
​

//VM参数: Xmx1024m -Xms1024m -XX:+PrintGCDetails

改参数测试:

package com.mvj;
​
import java.util.Random;
​
public class StackDemo2 {
    public static void main(String[] args) {
        // long maxMemory = Runtime.getRuntime().maxMemory();// 返回java
        // // 虚拟机试图使用的最大内存量
        // long totalMemory = Runtime.getRuntime().totalMemory();// 返回java
        //
        // System.out.println("maxMemory=" + maxMemory + "(字节)" + (maxMemory /
        // (double) 1024 / 1024) + "MB");
        // System.out.println("TotalMemory" + totalMemory + "(字节)" +
        // (totalMemory / (double) 1024 / 1024) + "MB");
​
        String str = "hello puzzle";
        while (true) {
            str += str + new Random().nextInt(888888888) + new Random().nextInt(999999999);
        }
​
    }
​
}
// VM参数: Xmx1024m -Xms1024m -XX:+PrintGCDetails

不改参数结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Arrays.java:3664)
    at java.lang.String.<init>(String.java:207)
    at java.lang.StringBuilder.toString(StringBuilder.java:407)
    at com.mvj.StackDemo2.main(StackDemo2.java:18)
​

 

改参数运行结果: 自动触发垃圾回收

[GC (Allocation Failure) [PSYoungGen: 1477K->502K(2048K)] 1477K->922K(7680K), 0.0022215 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1662K->505K(2048K)] 2082K->1617K(7680K), 0.0005040 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1962K->331K(2048K)] 5929K->4297K(7680K), 0.0004068 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 331K->283K(2048K)] 4297K->4249K(7680K), 0.0003533 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 283K->0K(2048K)] [ParOldGen: 3966K->2644K(5632K)] 4249K->2644K(7680K), [Metaspace: 2702K->2702K(1056768K)], 0.0066355 secs] [Times: user=0.09 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 30K->32K(2048K)] 4578K->4579K(7680K), 0.0005217 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 32K->32K(2048K)] 4579K->4579K(7680K), 0.0004910 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(2048K)] [ParOldGen: 4547K->3596K(5632K)] 4579K->3596K(7680K), [Metaspace: 2702K->2702K(1056768K)], 0.0026266 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->32K(2048K)] 5499K->5531K(7680K), 0.0003021 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(2048K)] [ParOldGen: 5499K->2644K(5632K)] 5531K->2644K(7680K), [Metaspace: 2702K->2702K(1056768K)], 0.0061275 secs] [Times: user=0.13 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 30K->0K(2048K)] 4577K->4547K(7680K), 0.0005018 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4547K->4547K(7680K), 0.0002321 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4547K->4547K(5632K)] 4547K->4547K(7680K), [Metaspace: 2702K->2702K(1056768K)], 0.0017545 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4547K->4547K(7680K), 0.0007669 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4547K->4533K(5632K)] 4547K->4533K(7680K), [Metaspace: 2702K->2702K(1056768K)], 0.0074809 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at com.mvj.StackDemo2.main(StackDemo2.java:18)
Heap
 PSYoungGen      total 2048K, used 61K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000)
  eden space 1536K, 4% used [0x00000000ffd80000,0x00000000ffd8f618,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 5632K, used 4533K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000)
  object space 5632K, 80% used [0x00000000ff800000,0x00000000ffc6d5f0,0x00000000ffd80000)
 Metaspace       used 2734K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 294K, capacity 386K, committed 512K, reserved 1048576K
​

GC面试题:

1.StackOverflowError和OutOfMemoryError,谈谈你的理解

如果当前线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OutOfMemoryError

这里把异常分为两种情况,但是存在一些相互重叠的地方:当栈空间无法继续分配时,到底是内存太小,还是已经使用的栈空间太大,本质上是对同一个问题的两种描述而已。

java.lang.OutOfMemoryError这个错误我相信大部分开发人员都有遇到过,产生该错误的原因大都出于以下原因:JVM内存过小、程序不严密,产生了过多的垃圾。

导致OutOfMemoryError异常的常见原因有以下几种:

  1. 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

  2. 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

  3. 代码中存在死循环或循环产生过多重复的对象实体;

  4. 使用的第三方软件中的BUG;

  5. 启动参数内存值设定的过小;

此错误常见的错误提示:

  1. tomcat:java.lang.OutOfMemoryError: PermGen space

  2. tomcat:java.lang.OutOfMemoryError: Java heap space

  3. weblogic:Root cause of ServletException java.lang.OutOfMemoryError

  4. resin:java.lang.OutOfMemoryError

  5. java:java.lang.OutOfMemoryError

 

2.一般什么时候会发生GC?如何处理?

答:Java中的GC会有两种回收:年轻代的MinorGC,和年老带的FullGC;如果此时老年代的内存空间不足就会触发FullGC,如果空间都不足抛出OutOfMemoryError.

3.GC回收策略,谈谈你的理解

答:年轻代(伊甸区和两个幸存区),GC回收策略为复制;

年老带的保存空间一般较大,GC回收策略为整理-压缩;

 

GC是什么?

  • 频繁收集Young区

  • 较少收集Old区

  • 基本不动Perm区

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。

因此GC按照的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(Minor Gc OR full GC)

  • 普通DC:(minor GC) 只针对新生代区域的GC.

  • 全局GC:(Minor GC OR Full GC) 针对老年代的GC,偶尔伴随对新生代的GC以及对永久代的GC

 

GC算法总体概述:

GC算法:

一、复制算法:Minor GC(普通GC) 较小收集 8:1:1

  • 年轻代(新生代)中使用的是MinorGC,这种算法采用的是复制算法.(copying)

  • what?

    • 原理:HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to). 默认比例为8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区每熬过一次Minor GC,年龄就增加一岁,当它的年龄达到一定程度时,就会被移动到老年代中。因为年轻代的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是讲内存分为两块,每次只用其中一块,当这一块内存用完,就将活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

      在GC开始的时候,对象只会存在于Eden区和名为From的Survivor区,Survivor区To是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到To,而在From区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到To区域。经过这次GC后,Eden区和From就是上次GC前的To.不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到To区被填满,To区被填满之后,会将所有对象移动到年老代中。

      因为Eden区域对象一般存活率较低,一般的,使用两块10%的内存作为空闲和活动区间,而另外的80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%的活动区间与另外80%中存活的对象转移到10%的空闲区间,接下来,将之前90%的内存全部释放,依次类推。

     

    • Minor GC会把Eden中的所有活的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的活的对象就被移到Old generation中,也即一旦收集后,Eden是就变成空的了。 当对象在Eden(包括一个Survivor区域,这里假设是from区域)出生后,在经过一次MinorGC后,如果对象还存活,并且能够被另一块Survivor区域所容纳(上面已经假设为from区域,这里应为to区域,即to区域有足够的内存空间来存储Eden和from区域中存活的对象),则使用复制算法将这些仍然活着的对象复制到另外一块Survivor区域(即to区域)中,然后清理所使用过的Eden以及Survivor区域(即from区域),并且将这些对象的年龄设置为1,以后对象在Survivor区每熬过一次Minor GC,就将对象的年龄+1,当对象的年龄达到某个值是(默认是15岁通过

      -XX:MaxTenuringThreshold来设定参数),这些对象就会成为老年代。

      -XX:MaxTenuringThreshold -设置对象在新生代中存活的次数

      好处:复制算法弥补了标记/清除算法中,内存布局混乱的缺点。

      坏处:如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍,复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变得不可忽视,所以从以上描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。

       

      标记清除

      标记清楚/标记整理算法: Full GC 又叫MajorGC(全局GC)

      老年达一般是由标记清除或者是标记清除与标记整合的混合实现

      • ==============================================

    • 标记清除(Mark-Sweep)

    • 标记整理(Mark-Compact)

    1.标记:(Mark)

    从根集合开始扫描,对存活的对象进行标记。

    2.清除:(Sweep)

    扫描整个内存空间,回收未被标记的对象,使用free-list记录可以区域。

    不需要额外空间

    两次扫描,耗时严重

    会产生内存碎片

    劣势:

    1.首先,它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差。

    2.其次,主要的缺点则是这种方式清理出来的空闲内存式不连续的,这点不难理解,我们的死亡对象都是随机的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然就会乱七八糟。而为了应付这一点,JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。

     

    标记整理 (Mark-Compact)

    Before GC After GC

    标记/整理算法的唯一缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。

     

    内存效率: 复制算法>标记清楚算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况并不一定如此)。

    内存整齐度:复制算法=标记整理算法>标记清除算法

    内存利用率:标记整理算法=标记清除算法>复制算法

     

    可以看出,效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面所提到的三个指标,标记/整理算法相对来说比较平滑一些,但效率上依然不尽如人,它比复制算法多了一个标记阶段,又比标记/清除多了一个整理内存的过程。

     

    难道没有一种最优算法么?。。。。。。

    回答: 无,没有最好的算法,只有最合适的算法。----->分代收集算法。

     

    面试题:

    1.JVM内存模型以及分区,详细到每个分区放什么

    2.堆里面的分区:Eden,Survival from to ,老年代,各自的特点

    3.GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别说说

    4.Minor GC 与Full GC 分别在什么时候发生

     

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值