内部存款和储蓄器败露,分而治之论各模块详细

2019-05-25 17:30 来源:未知

1. 上帝视角

Java垃圾回收机制

JVM内存模型可以分为两个部分,如下图所示,堆和方法区是所有线程共有的,而虚拟机栈,本地方法栈和程序计数器则是线程私有的。下面我们就来一一分析一下这些不同区域的作用。

【树看JVM】

1.1 GC机制作用

图片 1

 

1.2 堆内存3代分布(年轻代、老年代、持久代)

图片 2

【图看JVM】

图片 3

1.3 GC分类

2. 分而治之

1.4 GC过程

jvm-memory-model

 2.1 堆区

    构成:堆区由新生代和老年代组成,新生代中包含伊甸区(Eden)、幸存者区(survivor from 、survivor to)和老年代。

    GC:当创建新的对象时,对象首先会被放入Eden和survivor from中,每经历一次GC存活下来的对象,年龄都会加1。在进行了第一次GC后,在Eden中仍然存活的对象,将被放入survivor to ;而survivor from中的存活的对象将由年龄阀值(maxTenuringThreshold)来决定,如果年龄不超过maxTenuringThreshold的话,将被放入survivor to,如果超过的话,就被放入老年代。

    在经历GC之后,Eden和survivor from 都被清空,此时,原先的survivor from转变为survivor to的角色,原先的survivor to转变成survivor from的角色,下一次的GC将会依据上一个原理,继续进行。当survivor to满了之后,survivor to 中的对象将会被放入老年代。

    除了年龄阀值会触发对象晋升到老年代之外,还有以下第二种情况能够触发:

    前提:survivor to中,相同年龄的对象所占的空间超过一半。

    触发:对象的年龄超过了上述前提中对象的年龄,就会直接被放入老年代。

    因此,幸存者区存放的数据是Eden区GC后拷贝过来的数据,老年代存放的是数据是幸存者区GC后拷贝过来的数据

Java应用内存问题分析

堆内存

堆内存是所有线程共有的,可以分为两个部分:年轻代和老年代。下图中的Perm代表的是永久代,但是注意永久代并不属于堆内存中的一部分,同时jdk1.8之后永久代也将被移除。

图片 4

jvm-heap-memory

jvm-heap-memory

GC(垃圾回收器)对年轻代中的对象进行回收被称为Minor GC,用通俗一点的话说年轻代就是用来存放年轻的对象,年轻对象是什么意思呢?年轻对象可以简单的理解为没有经历过多次垃圾回收的对象,如果一个对象经历过了一定次数的Minor GC,JVM一般就会将这个对象放入到年老代,而JVM对年老代的对象的回收则称为Major GC。

如上图所示,年轻代中还可以细分为三个部分,我们需要重点关注这几点:

  1. 大部分对象刚创建的时候,JVM会将其分布到Eden区域。
  2. 当Eden区域中的对象达到一定的数目的时候,就会进行Minor GC,经历这次垃圾回收后所有存活的对象都会进入两个Suvivor Place中的一个。
  3. 同一时刻两个Suvivor Place,即s0和s1中总有一个总是空的。
  4. 年轻代中的对象经历过了多次的垃圾回收就会转移到年老代中。
  5. 当申请不到空间时会抛出 OutOfMemoryError。下面我们简单的模拟一个堆内存溢出的情况:
import java.util.ArrayList;
import java.util.List;
/* java -Xms20m -Xmx20m HeapOOM */
public class HeapOOM {
    static class OOMObject {
    }
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

下面是执行结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:261)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    at java.util.ArrayList.add(ArrayList.java:458)
    at HeapOOM.main(HeapOOM.java:13)

堆内存是我们平时在生产环境中进行性能调优中的一个非常重要的部分,对于这里我在另外一篇文章JVM垃圾回收算法及回收器详解有详细介绍,这里我们还是拓展补充几个常见的性能调优参数:

  • PretenureSizeThreshold: 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配。
  • MaxTenuringThrehold: 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就会加1,当超过这个参数值时就进入老年代。
  • UseAdaptiveSizePolicy: 动态调整Java堆中各个区域的大小以及进入老年代的年龄。
  • SurvivorRattio: 新生代Eden区域与Survivor区域的容量比值,默认为8,代表Eden: Suvivor= 8: 1。
  • NewRatio: 设置新生代(包括Eden和两个Survivor区)与老年代的比值(除去持久代),设置为3,则新生代与老年代所占比值为1:3,新生代占整个堆栈的1/4。
  • Xmx: 设置JVM堆最大内存。
  • Xms: 设置JVM堆初始内存。
  • Xmn: 参数设置了年轻代内存。

 2.2 栈区

        栈区中存储的是一个一个的帧(stack frame)**,每一个栈帧对应一个被调用的方法,它与方法的生命周期一致,随着方法的调用而被创建,随着方法的结束而销毁。栈帧中存放的是局部变量表(基本数据类型、引用),操作数栈、指向当前方法所属的类的运行时常量池的引用等信息。(运行时常量池在方法区中**)。

        图片 5

2.1 Java内存划分

方法区

方法区与Java堆一样,是各个线程共享的区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译(JIT)后的代码等数据。

对于JDK1.8之前的HotSpot虚拟机而言,很多人经常将方法区称为我们上图中所描述的永久代,实际上两者并不等价,因为这仅仅是HotSpot的设计团队选择利用永久代来实现方法区而言。同时对于其他虚拟机比如IBM J9中是不存在永久代的概念的。

其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap,而在JDK1.8之后永久代的该概念也已经不再存在取而代之的是元空间metaspace。

常量池其实是方法区中的一部分,因为这里比较重要,所以我们拿出来单独看一下。注意我们这里所说的运行时的常量池并仅仅是指Class文件中的常量池,因为JVM可能会进行即时编译进行优化,在运行时将部分常量载入到常量池中。

 2.3 方法区

    方法区由代码缓存、运行时常量池、持久代(JDK1.8后被删除)组成。

    · 持久代:在JDK1.6时方法区存放的是类的结构定义、方法、构造函数、以及存放静态变量,在JDK1.7之后,方法区的持久代被删除,取而代之的是元空间(metaSpace),元空间在本地内存中,与持久代类似,是对JVM规范中方法区的实现。

    · 代码缓存:代码缓存中存放的是JIT(just in time)编译器编译后的与硬件相关的代码。可以通过ReservedCodeCacheSize 和 InitialCodeCacheSize两个参数设置。

    · 运行时常量池:被所有线程共享,存储的是虚拟机加载Class之后的常量池数据(常量池是Class的一部分,存储着类、接口、方法中的常量)

2.2 Java常见内存问题

程序计数器

JVM中的程序计数器和计算机组成原理中提到的程序计数器PC概念类似,是线程私有的,用来记录当前执行的字节码位置。还是稍微解释一下吧,CPU的占有时间是以分片的形式分配给给每个不同线程的,从操作系统的角度来讲,在不同线程之间切换的时候就是依赖程序计数器来记录上一次线程所执行到具体的代码的行数,在JVM就是字节码。

 2.4 元空间(metaSpace)

    元空间在JDK1.7时开始取代方法区中的持久代(PermGen),那么,为什么要删除持久代,让元空间取而代之?官方解释如下图:

图片 6

    附官方地址:

    从官方的解释可以看出,由于JRokit中没有持久代而不需要配置,而删除持久代是官方为了融合JRockit与HotSpot所努力的一部分,所以在JDK1.7开始逐步开始取代持久代,到JDK1.8之后彻底删除。

    并且,类和方法的信息大小难以确定,永久带经常会发生内存溢出(OOM),java.lang.OutOfMemoryError: PermGen

(关于元空间的溢出,在下篇博文与其它内存溢出的异常一起整理,这里就不再赘述元空间的OOM)

【JVM】TroubleShooting之内存溢出异常(OOM)与调优:

2.3 ML OOM问题现象及分析

Java虚拟机栈

与程序计数器一样,Java虚拟机栈也是线程私有的,用通俗的话将它就是我们常常听说到堆栈中的那个“栈内存”。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表(局部变量表需要的内存在编译期间就确定了所以在方法运行期间不会改变大小),操作数栈,动态链接,方法出口等信息。每一个方法从调用至出栈的过程,就对应着栈帧在虚拟机中从入栈到出栈的过程。p.s: 关于栈帧这里我们以后讲虚拟机字节码执行引擎的时候再来仔细分析。

 2.5 本地方法栈

    与前面谈到的虚拟机栈类似,区别在于虚拟机栈为虚拟机调用的Java方法服务,本地方法栈是为虚拟机调用的本地方法(Native Method)服务。

Mative Method是一个Java调用一个非Java程序的接口,该方法的实现由非Java语言来实现


以下描述截取自《深入理解Java虚拟机:JVM高级特性与最佳实践》 作者: 周志明 

 

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。


 

2.4 IBM DUMP分析工具使用介绍

本地方法栈

本地方法栈和Java虚拟机栈类似,只不过是为JVM执行Native方法服务,这里就不解释了。

https://www.ziwenxie.site/2017/06/01/java-jvm-memory-model/

 2.6 程序计数器(PC)

    程序计数器记录的是线程下一步要执行的指令字节码地址。在多线程中,各线程之间是抢CPU的关系,要根据抢得时间片的CPU时间资源进行线程切换,因此,每个线程都有一个独立的程序计数器,记录各个线程下一步要执行的指令,这样才能够正常切换。

 

 

 

 

参考及推荐:

https://docs.oracle.com/javase/specs/jls/se8/jls8.pdf

https://blog.csdn.net/zjf280441589/article/details/53437703

 

Java应用CPU、线程问题分析

Java垃圾回收机制

1.GC机制作用

1.1 JVM自动检测和释放不再使用的对象内存

1.2 Java 运行时JVM会执行 GC,不再需要显式释放对象

例:Object.finallize()、 Windows.dispose()、 System.gc()

图片 7

2.Java堆3代分布

图片 8

关于Java堆3代分布情况,可通过命令:jmap –heap pid 查看

图片 9

3.GC分类

3.1 Young GC:收集生命周期短的区域

清空Eden from survivor中所有no ref的对象占用的内存

将Eden from survivor中所有存活的对象copy到to survivor中

一些对象将晋升到old中: to survivor放不下的或存活次数超过turning threshold中的

3.2 Full GC:收集生命周期短的区域和生命周期比较长的区域,对整个堆进行垃圾收集,有时也会回收持久区

清空heap中no ref的对象

清空permgen中已经被卸载的class信息

4.GC过程

新生成的对象在Eden区完成内存分配

当Eden区满,再创建对象,会因为申请不到空间触发YGC,进行young(eden 1survivor)区的垃圾回收(为什么是eden 1survivor:两个survivor中始终有一个survivor是空的,空的那个被标记成To Survivor)

YGC时,Eden不能被回收的对象被放入到空的survivor(也就是放到To Survivor,此时Eden被清空),另一个survivor(From Survivor)里不能被GC回收的对象也会被放入To Survivor,始终保证一个survivor是空的(YGC完成之后,To Survivor 和 From Survivor的标记互换)

YGC结束后,若存放对象的survivor满,则这些对象被copy到old区,或者survivor区没有满,但是有些对象已经足够Old(超过XX:MaxTenuringThreshold),也被放入Old区

当Old区被放满的之后,进行完整的垃圾回收,即 FGC

FGC后,若Survivor及old区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现OOM错误

图片 10

Java应用内存问题分析方法

1.Java内存划分

可粗略划分三类:

1.1 堆内存

存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理

图片 11

1.2 栈内存

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配(更准确地说是保存了引用的堆内存空间的地址,java中的“指针”)

1.3 永久保存区、方法区(Permanent Generation)

用于存储已被虚拟机加载的类信息、常量、静态变量等

图片 12

2.Java常见的内存问题表现形式:

2.1 OutOfMemory:内存溢出

2.2 Memory Leak:内存泄露

二者共同点:

通常最终的状态就会导致OOM错误

在Java堆或本地内存中都可能发生

二者不同点:

ML是已经分配好的内存或对象,当不再需要,没有得到释放 而OOM则是没有足够的空间来供jvm分配新的内存块

ML的内存曲线总体上是一条斜向上的曲线而OOM不是,反之未必

3.内存溢出类型:

虚拟机栈溢出、本地方法栈溢出、方法区溢出、堆溢出、运行时常量池溢出

异常类型:

java.lang.OutOfMemoryError: Java heap space

堆内存溢出

优化:通过-Xmn–Xms -Xmx参数手动设置 Heap的大小。

java.lang.OutOfMemoryError: PermGen space

PermGen Space溢出(方法区溢出、运行时常量池溢出)

优化:通过MaxPermSize参数设置PermGen space大小。

java.lang.StackOverflowError

栈溢出(虚拟机栈溢出、本地方法栈溢出)

优化:通过Xss参数调整

Demo代码 :

// Java 堆溢出publicstaticvoidmain(String[] args) { List list =newArrayList();while { list.add(newOOMObject; } }staticclass OOMObject { }// 虚拟机栈溢出publicstaticvoidmain(String[] args) {// TODO Auto-generated method stubSystem.out.println; }publicstaticintadd(){returnadd(); }// 方法区溢出publicstaticvoidmain(String[] args) {while { Enhancer enhancer =newEnhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache; enhancer.setCallback(newMethodInterceptor() { @OverridepublicObjectintercept(Objectobj, Method method,Object[] args, MethodProxy proxy)throwsThrowable {returnproxy.invoke(obj, args); } }); enhancer.create(); } }staticclass OOMObject { }20// 运行时常量池溢出publicstaticvoidmain(String[] args){// TODO Auto-generated method stubList list =newArrayList();inti =0;while{ list.add(String. valueOf.intern; } }// 内存泄露模拟publicstaticvoidmain(String[] args) {// TODO Auto-generated method stubList list =newArrayList(); Runtime run = Runtime.getRuntime();inti=1;while{int[] arr =newint[1024]; list.add;if(i 00==0){ System.out.print("最大堆内存=" run.maxMemory() /1024/1024 "M, "); System.out.print("已分配内存=" run.totalMemory() /1024/1024 "M, "); System.out.print("剩余空间内存=" run.freeMemory() /1024/1024 "M, "); System.out.println("最大可用内存=" ( run.maxMemory() - run.totalMemory() run.freeMemory /1024/1024 "M"); sleep; } } }publicstaticvoidsleep {try{ Thread.sleep; }catch(InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace(); } }

4.内存泄露现象

图片 13

heapspace:OutOfMemoryError

图片 14

开发人员的分析、解决思路

内存对象申请未释放

线程问题

分别从堆dump和线程dump进行分析:

jmap -dump:format=b,file=heap.dump pid

jstack pid >> thread.dump

5.JAVA DUMP分析工具

IBM HeapAnalyzer:ha456.jar

IBM Thread and Monitor Dump Analyzer:jca457.jar

堆dump分析

占用内存较多代码块

分析代码快上下文

分析占用内存的对象内容

图片 15

线程dump分析

活跃线程

阻塞线程

等待资源线程

图片 16

Java应用CPU问题分析方法

1.程序响应慢,CPU高

ThreadDump

jstack pid >> thread.dump

找到导致cpu高的线程 top -H -p pid

pid 十进制转十六进制

找到对应的线程UE打开 threaddump文件查找:按十六进制关键字找到对应的线程,把相关的方法找出来,可以精确到代码的行号

2.程序响应慢,CPU不高

一般表现为thread struck在了i/o、db等

实例:

IO阻塞

线程状态为“in Object.wait()”,说明正在等待线程池可用资源,由于线程池满导致新的IO请求处于排队等待状态,且发生在:at com.iflytek.diange.data.provider.sendsong.impl.SendSongImpl.getSendSongInfosByUserId(SendSongImpl.java:92)行

图片 17

3.程序无响应

死锁

线程状态为“waiting to lock”: 两个线程各持有一个锁,又在等待另一个锁,故造成死锁,且发生在DeadLockTest.java:39行

图片 18

版权声明:本文由韦德娱乐1946_韦德娱乐1946网页版|韦德国际1946官网发布于网络编程,转载请注明出处:内部存款和储蓄器败露,分而治之论各模块详细