• 实战java虚拟机-jvm故障诊断与性能优化-读书笔记


    jvm基本结构


    • 栈和函数调用关系

    局部变量表

    当调用的函数的局部变量个数不同时,会影响递归的深度
    局部变量在函数调用结束后,会随着函数销毁
    public class TestStackDeep {
    	private static int count=0;
    	public static void recursion(long a,long b,long c){
    		long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10;
    		count++;
    		recursion(a,b,c);
    	}
    	public static void recursion(){
    		count++;
    		recursion();
    	}
    	public static void main(String args[]){
    		try{
    			recursion(0L,0L,0L);
    			//recursion();
    		}catch(Throwable e){
    			System.out.println("deep of calling = "+count);
    			//e.printStackTrace();
    		}
    	}
    }
    
    localvar2的b变量可以复用a的位置
    localvar1就不行
    public class LocalVar {
    	public void localvar1(){
    		int a=0;
    		System.out.println(a);
    		int b=0;
    	}
    	public void localvar2(){
    		{
    		int a=0;
    		System.out.println(a);
    		}
    		int b=0;
    	}
    	public static void main(String[] args) {
    		
    	}
    
    }
    
    public class LocalVarGC {
        public void localvarGc1() {
            byte[] a = new byte[6 * 1024 * 1024];
            System.gc();
        }
    申请空间后立即回收,因为a还在被引用,所以无法回收
        public void localvarGc2() {
            byte[] a = new byte[6 * 1024 * 1024];
            a = null;
            System.gc();
        }
    垃圾回收前将变量a设置为null,byte数组失去引用,所以可以回收
        public void localvarGc3() {
            {
                byte[] a = new byte[6 * 1024 * 1024];
            }
            System.gc();
        }
    垃圾回收前,局部变量a失效,虽然a已经离开了作用域,但a还在局部变量中,所以不能回收
        public void localvarGc4() {
            {
                byte[] a = new byte[6 * 1024 * 1024];
            }
            int c = 10;
            System.gc();
        }
    回收之前,a失效,并且c重用了a局部变量的位置,所以a已经被销毁,可以回收
        public void localvarGc5() {
            localvarGc1();
            System.gc();
        }
    可以回收
        public static void main(String[] args) {
            LocalVarGC ins = new LocalVarGC();
    //        ins.localvarGc1();
    //        ins.localvarGc2();
    //        ins.localvarGc3();
            ins.localvarGc4();
        }
    

    操作数栈

    保存计算中间结构

    帧数据区

    保存这访问常量池的指针,方便程序访问常量池
    保存着异常处理表

    栈上分配:对于那些线程私有对象,可以分配栈上,而不是堆上,可以避免垃圾回收

    如果生成大量反射对象,则有可能元数据空间不够用

    	public static void main(String[] args) {
    		int i = 0;
    		try {
    			for (i = 0; i < 10000; i++) {
    				CglibBean bean = new CglibBean("geym.zbase.ch2.perm" + i, new HashMap());
    			}
    		} catch (Exception e) {
    			System.out.println("total create count:" + i);
    			throw e;
    		}
    	}
    

    jdk1.7设置方法区(永久区)大小: -XX:PermSize=10m -XX:MaxPermSize=10m
    jdk1.8设置元数据区: -XX:MaxMetaspaceSize=10m

    常用jvm虚拟机参数

    打印gc日日志到文件: -XX:+PrintGCDetails -Xloggc:D:/a.log

    • 类加载/卸载跟踪
      查看类的卸载过程:-XX:+TraceClassUnloading
      查看类的加载过程:-XX:+TraceClassLoading
      输出结果:
    Example是通过反射生成的类
    [Loaded Example from __JVM_DefineClass__]
    [Unloading class Example 0x00000007c006a028]
    

    打印虚拟机接收到的命令行显式参数-XX:+PrintVMOptions

    • 堆溢出
    public class DumpOOM {
        public static void main(String[] args) {
            Vector v=new Vector();
            for(int i=0;i<25;i++)
                v.add(new byte[1*1024*1024]);
        }
    }
    

    -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/a.dump

    java.lang.OutOfMemoryError: Java heap space
    Dumping heap to D:/a.dump ...
    Heap dump file created [15108906 bytes in 0.012 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	at geym.zbase.ch3.heap.DumpOOM.main(DumpOOM.java:19)
    

    内存溢出时导出所有参数: -XX:+HeapDumpOnOutOfMemoryError

    • 配置栈大小: -Xss

    垃圾回收概念与算法

    引用计数,标记压缩法,标记清除法,复制算法和分代,分区

    引用计数法

    对于一个对象A,只要有任一个对象引用了A,则A的引用计数+1,当引用失效则引用计数-1,引用计数为0,则A不可以再被使用
    缺点:
    1.无法处理循环引用的情况
    2.引用计数器在每次引用产生和消除的时候,需要一个加法和减法操作,对性能有一些影响


    标记清除法

    分为两个阶段:
    1.标记阶段:首先通过根节点,标记所有从根节点能到达的对象,没有被标记的就是垃圾对象
    2.清除阶段: 清理所有没有被标记的对象
    缺点: 产生内存碎片

    标记压缩算法

    1.标记阶段:首先通过根节点,标记所有从根节点能到达的对象,没有被标记的就是垃圾对象
    2.压缩阶段:将存活对象压缩到内存的一端,然后清理剩余的空间

    复制算法

    将所有内存分为两块,每次只使用一块
    在垃圾对象多,存活对象少的地方适合使用

    分代算法

    卡表:表示老年代某一区域的所有对象是否持有新生代对象的引用
    这样在新生代GC时,可以不用花大量时间扫描所有老年代对象,来确定每一个对象的引用关系
    当卡表标志位为1时,才需要扫描老年代的对象,标志位为0则代表所在区域的老年代对象都没有对新生代对象的引用

    分区算法

    将整个堆空间划分为连续的小空间,每一个区域独立使用,独立回收,可以减少GC时间

    真正的垃圾:判断可触及性

    可触及性分3种状态:
    1.可触及的
    2.可复活的:对象引用被释放,但是对象有可能在finalize()方法中复活
    3.不可触及的:对象的finalize()方法被调用,并且没有复活,就会进入不可触及状态,对象永远不会被复活,因为finalize()只会被调用一次

    第一次设置为null时,调用gc,发现对象被复活了,再次释放对象引用并执行gc,对象才真正被回收
    因为finalize只会执行一次,第二次没有执行finalize,所以对象没有被复活
    public class CanReliveObj {
    	public static CanReliveObj obj;
    	@Override
    	protected void finalize() throws Throwable {
    		super.finalize();
    		System.out.println("CanReliveObj finalize called");
    		obj=this;
    	}
    	@Override
    	public String toString(){
    		return "I am CanReliveObj";
    	}
    	public static void main(String[] args) throws InterruptedException{
    		obj=new CanReliveObj();
    		obj=null;
    		System.gc();
    		Thread.sleep(1000);
    		if(obj==null){
    			System.out.println("obj 是 null");
    		}else{
    			System.out.println("obj 可用");
    		}
    		System.out.println("第二次gc");
    		obj=null;
    		System.gc();
    		Thread.sleep(1000);
    		if(obj==null){
    			System.out.println("obj 是 null");
    		}else{
    			System.out.println("obj 可用");
    		}
    	}
    }
    

    引用和可触及性强度

    java中4个级别:强,软,弱,虚

    • 强引用

    • 软引用
      通过强引用建立软引用: SoftReference<User> userSoftRef = new SoftReference<User>(u);
      GC未必会回收软引用对象,但是,内存资源紧张时,软引用会被回收,所以软引用不会导致内存溢出

    • 弱引用
      发现即被回收,不管当前内存空间足够与否,都会回收它的内存
      通过强引用建立弱引用: WeakReference<User> userWeakRef = new WeakReference<User>(u);

    • 虚引用

      作用:跟踪对象垃圾回收的情况
      当垃圾回收器准备回收一个对象时,如果发现还存在虚引用,就会在回收对象后,将这个虚引用加入到引用队列,以通知应用程序对象的回收情况

    垃圾收集器和内存分配

    串行收集器

    使用单线程进行垃圾回收的回收器,每次回收时,只有一个线程在工作

    • 新生代串行收集器

    特点:
    1.使用单线程进行垃圾回收
    2.独占式垃圾回收

    -XX:UseSerialGC 指定使用新生代串行收集器和老年代串行收集器
    -XX:+UseParNewGC 新生代使用ParNew回收器,老年代使用串行收集器
    -XX:+UseParallelGC 新生代使用ParallelGC收集器,老年代使用串行收集器

    • 老年代串行收集器

    老年代串行收集器使用标记压缩算法
    因为老年代收集时间一般比新生代长,一旦老年代收集器启动,应用程序会停顿很长时间

    并行收集器

    • 新生代ParNew收集器

    只是简单将串行收集器多线程化,它的回收策略,算法,以及参数和新生代串行回收器一样

    • 新生代ParllelGC回收器
      特点:特别关注系统吞吐量,支持自适应GC调节策略
      -XX:+UseParallelGC: 新生代使用ParallelGC回收器,老年代使用串行回收器
      -XX:+UseParallelOldGC:新生代使用ParallelGC回收器,老年代使用ParallelOldGC回收器
      -XX:MaxGCPauseMillis: 设置最大垃圾收集停顿时间
      -XX:GCTimeRatio: 设置吞吐量的大小(0-100的整数,默认值是19)
      -XX:+UseAdaptiveSizePolicy: 打开自适应GC策略,
      在这种模式下,新生代的大小,eden和survivor的比例,晋升老年代的年龄参数会自动调整

    • 老年代ParallelOldGC
      特点:也是关注吞吐量的收集器,使用标记压缩算法

    • CMS回收器
      特点: (Concurrent Mark Sweep)并发标记清除, 主要关注系统停顿时间
      主要步骤:
      初始标记,并发标记,预清理,重新标记,并发清除和并发重置
      其中初始标记和重新标记是独占系统资源的

      -XX:+UseConcMarkSweepGC:启用CMS垃圾回收器

    • G1回收器
      主要工作区域是eden区和survivor区
      4个阶段:
      1.新生代GC
      2.并发标记周期
      3.混合收集
      4.如果需要,会进行fullGC

    • 在TLAB上分配对象: 线程本地分配缓存
      为了加速对象分配而生
      将一部分对象不分配在堆上,而是分配在线程专用内存分配区域

    锁优化:
    1.减少持有时间
    尽可能减少某个锁的占有时间,减少线程间互斥可能
    2.减少锁粒度
    ConcurrentHashMap使用拆分锁对象方式提高吞吐量
    3.锁分离
    读写锁
    4.锁粗化
    如果遇到一连串对同一锁不断进行请求和释放,便把所有锁操作整合成对锁的一次请求,
    从而减少对锁请求同步次数
    5.无锁化

    java内存模型

    对于并发程序,如果一个线程修改了全局变量A,在另外一个线程不一定能读取到最新的值
    java内存模式就是解释和规范这种情况的,将这种看似随机的状态变为可控
    从而屏蔽多线程可能引发的问题

  • 相关阅读:
    看别人的代码学习的css
    Font Awesome
    响应式网站设计
    css兼容性的问题
    英语
    我的bootstrap使用的历程
    jquery的常用的容易忘记的东西
    jquery基本方法
    js与jquery的区别
    134123
  • 原文地址:https://www.cnblogs.com/Baronboy/p/15240502.html
Copyright © 2020-2023  润新知