博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JVM学习总结
阅读量:2399 次
发布时间:2019-05-10

本文共 8834 字,大约阅读时间需要 29 分钟。

JVM 学习总结

PC寄存器
    - 每个线程拥有一个PC寄存器
    - 在线程创建时被创建
    - 指定下一条指令的地址
    - 执行本地方法时,PC的值是undefined
方法区
    - 保存装载的类信息
        - 类型的常量池
        - 字段,方法信息
        - 方法字节码
        【注意】:
                            JDK6:String等常量信息存在于方法区
                            JDK7:String等常量信息存在于堆内存
    - 通常和永久区(Perm)关联在一起(永久区通常保存一些相对稳定相对静止的信息;通常是由Java虚拟机维护)
    
Java堆内存
    - 和程序开发密切相关
    - 应用系统对象都保存在Java堆中(new Object)
    - 所有线程共享Java堆
    - 对分代GC来说,堆也是分代的
    - GC的主要工作区间
    【注意】:堆内存每次分配后需要手动清理空间
    
Java栈内存(数据结构:先进后出)
    - 线程私有
    - 栈由一系列帧组成(因此Java栈也叫做帧栈)
    - 帧保存一个方法的局部变量,操作数栈,常量池指针
    - 每一次方法调用创建一个帧,并压栈
    【注意】:栈内存每次分配后无需手动清理空间,函数调用完成自动清理
    
Java栈 - 局部变量表 【包含参数和局部变量】
    
Java栈 - 栈上分配
    - 一般小对象(几十个bytes),在没有逃逸(线程共用的对象)的情况下,可以直接分配在栈上
    - 一般一个栈的空间大概是几百KB到1M左右(尽量分配给小对象)
    - 直接分配在栈上,可以自动回收,减轻GC压力
    - 大对象或者逃逸对象无法栈上分配
    
堆,栈,方法区交互
    - 局部变量实例化后是存入堆中,栈中会存放该对象的引用
    - 类本身的信息,方法的字节码是在方法区当中存放
内存模型
    - 每一个线程有一个工作内存和主存独立
    - 工作内存存放主存中变量的值的拷贝
    
    当数据从主内存复制到工作内存时,必须出现两个动作
        第一:由主内存执行的读(read)操作
        第二:由工作内存执行的相应的load操作
    当数据从工作内存拷贝到主内存时,也出现两个操作
        第一:由工作内存执行的存储(store)操作
        第二:由主内存执行的相应的写(write)操作
    
    每一个操作都是原子的,即执行期间不会被中断
    对于普通变量,一个线程中更新的值,不能麻烦反应在其他变量中
    如果需要在其他线程中立即可见,需要使用volatile关键字
    
    可见性:
            一个线程修改了变量,其他线程可以立即知道
    
    保证可见性的方法
        - 使用volatile关键字
        - synchronized(unlock之前,写变量值回主存)
        - final(一旦初始化完成,其他线程就可见)
    
    有序性:
        - 在本线程内,操作都是有序的
        - 在多线程的情况下,操作都是无序的(指令重排或主内存同步延时)
        
    指令重排:
        - 线程内串行语义
            - 写后读    a=1;b=a;    写一个变量之后,再读这个位置。
            - 写后写    a=1;a=2;    写一个变量之后,再写这个变量。
            - 读后写    a=b;b=1;    读一个变量之后,再写这个变量。
            - 以上语句【不可】重排
            - 编译器不考虑多线程间的语义
            - 可重排:a=1;b=2;
    
    指令重排的基本原则
        -    程序顺序原则:一个线程内保证语义的串行性
        - volatile规则:volatile先写,后读
        - 锁规则:解锁(unlock)必然发生在随后的加锁(lock)之前
        - 传递性:指令A先于指令B,指令B先于指令C,那么指令A必然先于指令C
        - 线程的start方法限于它的每一个动作
        - 线程的所有操作先于线程的中介(Thread.join())
        - 线程的中断(interrupt())先于被中断的线程的代码
        - 对象的构造函数执行结束先于finalize()方法
    
    解释运行
        - 解释执行以解释方式运行字节码
        - 解释执行的意思:读一句执行一句
        
    编译执行(JIT: Just In Time)
        - 将字节码编译成机器码
        - 直接执行机器码
        - 运行时编译
        - 编译后性能有数量级的提升
    【注意】:解释运行和编译运行之间的性能差10倍左右
    
JVM常用参数配置
    
    - Trace跟踪参数
    
        1. -verbose:gc
        
        2. -XX:+PrintGC(发生GC时则会打印相关的GC信息)
            示例:- [GC 4790k->374k(15872k), 0.0001606 secs]
            解释:GC回收前是占用4790k的空间,回收之后只占用374k,总大小为15872k
            
        3. -XX:+PrintGCDetails(打印GC详细信息)
            示例1:- [GC[DefNew: 4416k->0k(4928k),0.0001897 secs] 4790k->374k(15872k),0.0002232 secs]
                            [Times: user=0.00 sys=0.00 real=0.00 secs]
                            
            示例2:-XX:+PrintGCDetails在程序执行结束后打印的堆的信息
                            - Heap
                            - def new generation total 13824k used 11223k [0x27e80000,0x28d80000,0x28d80000]
                            e.x: 新生代共有13M可用,已用11M [低边界,当前边界(所使用,所分配到的位置),最大边界]
                            计算:(0x28d80000-0x27e80000)/1024/1024=15M
                                        也就是说新生代被分配了15M
                                        正好就是EdenSpace(12288)+FromSpace(1536)+ToSpace(1536)=15M
                                        EdenSpace(12288)+FromSpace(1536)=NewGeneration(13824)
                            - eden space 12288k 91% used [0x27e8000,0x28975f20,0x28a80000]
                            e.x: 伊甸园公有12M可用,已用91%
                            - from space 1536k 0% used [0x28a80000,0x28a80000,0x28c00000]
                            e.x: 幸存代
                            - to space 1536k 0% used [0x28c00000,0x28c00000,0x28d80000]
                            e.x: 幸存代
                            - tenured generation total 5120k used 0k [0x28d80000,0x29280000,0x34680000]
                            e.x: 老年代共有5M可用,已用0k
                            - the space 5120k 0% used [0x28d80000,0x28d80000,0x28d80200,0x29280000]
                            - compacting perm gen total 12288k used 142k [0x34680000,0x35280000,0x38680000]
                            e.x: 永久区共有12M可用,已用142k
                            - the space 12288k 1% used [0x34680000,0x346a3a90,0x346a3c00,0x35280000]
                            - ro space 10240k 44% used [0x38680000,0x38af73f0,0x38af7400,0x39080000]
                            e.x: 只读共享区
                            - rw space 12288k 52% used [0x3980000,0x396cdd28,0x396cde00,0x39c80000]
                            e.x: 可读可写区
                            
        4. -XX:+PrintGCTimeStamps(打印GC发生的时间戳)
        
        5. -Xloggc:log/gc.log(重定向GC信息到日志文件)
        
        6. -XX:+PrintHeapAtGC(每一次GC前后,都打印【堆】信息)
        
        7. -XX:+TraceClassLoading(监控系统中每个类的加载)
        
        8. -XX:+PrintClassHistogram(按下Ctrl+Break后,打印类的信息)
        示例:
                    num            #instances            #bytes        class name
                    序号        实例数量                总大小        类型
        
    - 堆的分配参数
        
        1. -Xmx -Xms
            指定最大堆内存和最小堆内存
            示例:-Xmx20m -Xms5m
                        使用:Xmx = Runtime.getRuntime().maxMemory()/1024/1024
                                    FreeMemory = Runtime.getRuntime().freeMemory()/1024/1024
                                    TotalMemory = Runtime.getRuntime().totalMemory()/1024/1024
                        结果:
                        堆内存初始值
                        Xmx=18.0M
                        FreeMemory=6.200019836425781M
                        TotalMemory=7.0M
                        
                        分配了1M空间给byte数组
                        Xmx=18.0M
                        FreeMemory=5.200004577636719M
                        TotalMemory=7.0M
                        
                        分配了4M空间给数组
                        Xmx=18.0M
                        FreeMemory=5.699989318847656M
                        TotalMemory=11.5M
                        
                        回收内存
                        Xmx=18.0M
                        FreeMemory=6.994834899902344M
                        TotalMemory=11.5M
                        
                        观察Xmx、FreeMemory、TotalMemory变化情况
        2. -Xmn
                    设置新生代大小(EdenSpace+FromSpace+ToSpace的总和)
            
        3. -XX:NewRatio
                     新生代(eden + 2*s)和老年代(不包含永久区)的比值
                     4表示 新生代:老年代=1:4
                     即:年轻代占堆内存的1/5
            
        4. -XX:SurvivorRatio
                     设置两个Survivor区和Eden的占比
                     8表示 两个Servivor:Eden = 2:8
                     即:一个Servivor占年轻代的1/10
                     
        【注意】:理论情况下,发生GC的次数越多,对系统性能的损耗越大
                            Eden区调大一点,幸存代调小一点,有利于减少GC次数    
        
        5. -XX:+HeapDumpOnOutOfMemoryError
                    OOM(Out Of Memory)时导出堆到文件
        
        6. -XX:HeapDumpPath
                    导出OOm的路径
                    示例:-Xmx20M -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump
        
        【总结】:
                - 根据实际情况调整新生代和幸存代的大小
                - 官方推荐新生代占堆内存的3/8
                - 幸存代占新生代的1/10
                - 在OOM时,记得Dump出堆信息,确保可以排查现场问题
                
        7. -XX:PermSize -XX:MaxPermSize
                    设置永久区的初始空间和最大空间
                    他们表示一个系统可以容纳多少个类型(一般系统几十兆或者几百兆就够用了)
            
    - 栈的分配参数
        
        -Xss
            - 通常只有几百KB
            - 决定了函数调用的深度
            - 每个线程都有独立的栈空间
            - 局部变量、参数分配在栈上
        【注意】:若想让系统多跑一些线程,应该把栈空间尽量减少,而不是增大
                             若系统中有“递归”调用,栈空间不宜太小,若太小,可能会造成OOM
                             若出现java.lang.StackOverflowError,说明函数调用的深度太深,需要调把栈空间调大一点
                             若想让函数尽可能被多调用的方式:减少局部变量(栈帧里面的局部变量表包含参数和函数当中的局部变量)
                             若能减少局部变量的数量,就能够减少每次函数调用所消耗的空间,这样能够让函数多被调用几次
            
【GC算法】
    标记-清除算法
        标记-清除算法是现代垃圾回收算法的思想基础。
            标记-清除算法是将垃圾回收分为两个阶段
                1.标记阶段
                2.清除节点
            一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。
            因此未被标记的对象就是未被引用的垃圾对象。然后,在清除节点,清除所有未被标记的对象。
            
    标记-压缩算法
        标记-压缩算法适合用于存活对象较多的场合,如老年代。
        它在标记-清除算法的基础上做了一些优化。
        和标记-清除算法一样,标记-压缩算法首先需要从根节点开始,对所有可达对象做一次标记
        之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。
        最后清理边界外所有的空间。
        
    【注意】:标记-清除算法执行后,内存可能会产生一些碎片
                         标记-压缩算法执行后,内存空间是连续的,没有碎片
    
    复制算法(对内存空间有一定的浪费)
        与标记-清除算法相比,复制算法是一种相对高效的回收方法
        不适用于存活对象较多的场合 如老年代
        将原有的内存空间分为两块(两块大小完全相同),每次只使用其中一跨,在垃圾回收时
        将正在使用的内存中的存活对象复制到未使用的内存块中
        之后清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
        
    分代思想
        依据对象的从怒火周期进行分类,短命对象归为新生代,长命对象归为老年代
        根据不通代的特点,选取合适的收集算法
        - 少量对象存活,适合复制算法
        - 大量对象存活,适合标记-清理或者标记-压缩算法
    
    GC算法总结
        引用计数(没有被Java采用)【缺点】:没有办法处理循环引用
        标记-清除 老年代使用
        标记-压缩 老年代使用
        复制算法  新生代使用
    
    对象产生在Eden区,使用标记-清除算法
    From区和To区空间大小一样,使用复制算法
    
    垃圾对象
        可触及性
            - 从根节点可以触及到这个对象
                什么是根节点
                - 节点可以认为是栈中引用的对象
                - 方法区中静态成员或者常量引用的对象(全局对象)
                - JNI方法栈中引用的对象
        可复活的
            - 一旦所有引用被释放,就是可复活状态
            - 因为在finalize()中可能复活该对象
        不可触及的
            - 在finalize()后,可能会进入不可触及状态
            - 不可触及的对象不可能复活
            - 可以回收
    
    串行回收器
        - 最古老,最稳定
        - 效率高
        - 可能会产生较长的停顿
        - 在多核计算机中可能发挥不了最大的作用(原因:单线程)
        - -XX:+UseSerialGC
            - 新生代,老年代使用串行回收器
            - 新生代复制算法
            - 老年代标记-压缩
            
    并行收集器ParNew(同样会产生Stop-The-World,即:停止用户应用程序的线程)
        - -XX:+UserParNewGC(New:代表新生代)
            - 新生代并行
            - 老年代串行
        - Serial收集器新生代的并行版本
        - 复制算法
        - 多线程,需要多核支持
        - -XX:ParallelGCThreads 限制线程数量    
        
    并行收集器Parallel(同样会产生Stop-The-World,即:停止用户应用程序的线程)
        - 类似ParNew
        - 新生代复制算法
        - 老年代标记-压缩
        - 更加关注吞吐量
        - -XX:+UseParallelGC
            使用Parallel收集器(新生代并行+老年代串行)
            -XX:+UserParallelOldGC
            使用Parallel收集器(新生代并行+老年代并行)
            
    -XX:MaxGCPauseMills
        - 最大停顿时间,单位毫秒
        - GC尽力保证回收时间不超过设定值
    
    -XX:GCTimeRatio
        - 0-100的取值范围
        - 垃圾收集时间占总时间的比值
        - 默认99,即:最大允许1%时间做GC
        
    【注意】:以上两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优
    
    CMS收集器(和应用程序的线程一起执行)
        - Concurrent Mark Sweep 并发标记-清除
        - 标记-清除算法
        - 与标记-压缩算法相比
        - 并发节点会降低吞吐量
        - 单纯老年代收集器(新生代使用ParNew)
        - -XX:+UseConcMarkSweepGC
        
    CMS运行过程比较复杂,着重实现了标记的过程,可分为
        - 初始标记(会产生全局停顿)
                1.根可以直接关联到的对象
                2.速度快
        - 并发标记(和用户应用程序一起)
                主要标记过程,标记全部对象
        - 重新标记(会产生全局停顿)
                1.由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
        - 并发清除(和用户应用程序一起)
                1.采用标记-清除算法
                    原因:因为CMS在执行时,是和应用程序并发执行,
                                而因为标记-压缩算法是需要将存活的对象移动到堆内存的一端
                                此时,应用程序有可能找不到存活的对象
                                而标记-清除算法只是清除不可达的对象,没有对他们进行移动
                2.基于标记结果,直接清理对象
    
    CMS的特点
        - 尽可能降低停顿
        - 会影响系统整体吞吐量和性能
            比如:用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半
        - 清理不彻底
            因为在清理节点,用户线程还在运行,会产生新的垃圾,无法清理
        - 因为和用户线程一起运行,不能在空间快满时再清理
            - -XX:CMSInitiatingOccupancyFraction设置出发GC的阈值
            - 如果不幸内存预留空间不足,就会引起Concurrent mode failure这个错误!!!
            【经验】:若遇到Concurrent mode failure错误,使用串行收集器作为后备
                                    因此,应用程序可能会产生一段时间的停顿
        
        - -XX:+UseCMSCompactAtFullCollection  Full GC之后,进行一次整理
                整理过程是独占的(不与其他线程并发),会引起停顿时间变长
        - -XX:+CMSFullGCsBeforeCompaction
                设置进行几次Full GC之后,进行一次碎片整理
        - -XX:ParallelCMSThreads
                设置CMS的线程数量(一般情况下,约等于CPU的可用线程数量,不宜设置的太大)
        
        【注意】:为了减轻GC压力,我们需要注意些什么?
                            1.软件如何设计架构
                            2.代码如何写
                            3.堆空间如何分配
                            
    总结GC参数
        1. -XX:+UseSerialGC 在新生代和老年代使用串行收集器
        2. -XX:SurvivorRatio 设置Eden区和Survivor区大小的比例
        3. -XX:NewRatio    新生代和老年代的比例
        4. -XX:+UseParNewGC    在新生代使用并行收集器
        5. -XX:+UseParallelGC    新生代使用并行回收收集器
        6. -XX:+UseParallelOldGC    老年代使用并行回收收集器
        7. -XX:ParallelGCThreads=4    设置用于垃圾回收的线程数
        8. -XX:+UseConcMarkSweepGC    新生代使用并行收集器,老年代使用CMS+串行收集器
        9. -XX:ParallelCMSThreads    设置CMS的线程数量
        10. -XX:CMSInitiatingOccupancyFraction=50    设置CMS收集器在老年代空间被使用多少后触发
        11. -XX:+UseCMSCompactAtFullCollection    设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的处理
        12. -XX:CMSFullGCsBeforeCompaction=2    设置进行多少次CMS垃圾回收后,进行一次内存压缩
        13. -XX:+CMSClassUnloadingEnabled    允许对类元数据进行回收
        14. -XX:CMSInitiatingPermOccupancyFraction    当永久区占用率达到此值设置的百分比时,启动CMS回收
        15. -XX:UseCMSInitiationOccupancyOnly    表示只在到达阈值的时候,才进行CMS回收
        16. -XX:+DisableExplicitGC 表示禁用代码中System.gc()
        17. -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。
                如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。
                对于年老代比较多的应用,可以提高效率。
                如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,
                这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
        18. -XX:+PrintTenuringDistribution 打印新生代年龄分布
        19. -XX:-ReduceInitialCardMarks 解决放入大对象导致JVM Crash
        20. -XX:+CMSParallelRemarkEnabled 并行标记
        21. -XX:+UseCompressedOops JVM压缩普通对象指针
        
        
    jvisualVM监控配置参数:
        -Dcom.sun.management.jmxremote=true (开启jvisualVM监控)
        -Djava.rmi.server.hostname=xxx.xxx.xxx.xxx (主机名)
        -Dcom.sun.management.jmxremote.port=xxxxx (端口)
        -Dcom.sun.management.jmxremote.ssl=false (是否使用ssl)

        -Dcom.sun.management.jmxremote.authenticate=false (是否需要用户名密码进行验证)

    并且在jdk中需要修改java.policy, 在最后一行追加permission java.security.AllPermission;

    并启动jdk中的jstatd这个服务。就可以使用jVisualVM监控JVM的各项参数变化了。

   

欢迎访问我的个人Github

转载地址:http://ekjob.baihongyu.com/

你可能感兴趣的文章
在Linux下配置TCP/IP(转)
查看>>
深入理解硬盘的 Linux 分区(转)
查看>>
Linux 2.4中netfilter框架实现(转)
查看>>
第一次备份与紧急系统恢复(转)
查看>>
安装、完善slackware的全部过程(转)
查看>>
Windows+Apache+resin配置(转)
查看>>
proxy 相关问题集(转)
查看>>
Linux下NFS网络文件系统设定及管理(转)
查看>>
ORACLE常用傻瓜问题1000问(之十二)(转)
查看>>
已经装了最新的binutils,为什么grub还是不能用(转)
查看>>
网络管理员指南 -10.网络信息系统 -1>熟悉NIS(转)
查看>>
巧用打印口制作笔记本密码破解器(转)
查看>>
Oracle 8 的函数介绍(转)
查看>>
CVSClient/Server连接设置(转)
查看>>
hosts.equiv和.rhosts文件(转)
查看>>
Linux关机命令详解(转)
查看>>
cron的使用(转)
查看>>
javascript动态隐藏显示技术(转)
查看>>
硬盘主引导记录详解(转)
查看>>
安装J2SE 1.3.1 for Linux的方法(转)
查看>>