精华内容
下载资源
问答
  • 2021/8/24
  • JVM的面试题,看你能答出几? 请你谈谈你对JVM的理解?Java8虚拟机和之前的变化更新? 什么是OOM,什么是StackOverFlowError?怎么分析? JVM的常用调优参数有哪些? 内存快照如何抓取,怎么分析Dump文件? ...

    目录

    一、JVM的位置

    二、JVM的体系结构

    三、类加载器

    1.类加载器举例

    2. JVM中提供了三层的ClassLoader

    3. 双亲委派机制(重要)

    3.1 工作原理

    3.2.优点

     四、沙箱安全机制(了解)

    1、什么是沙箱

    2、组成沙箱的基本组件

    五、Native (重要)

    八、PC寄存器

    九、方法区

    十、堆

    1、堆里面存放什么

    2、新生区

    3、养老区

    4、永久区

    十一、出现OOM(栈溢出)

    十二、GC的算法

    1. 引用计数法

    2. 复制算法

    3. 标记清除

    3.1 标记清除

    3.2 标记压缩

    3.3 标记清除压缩

    4. 算法总结

    十二、JMM


     

    JVM的面试题,看你能答出几个?

    • 请你谈谈你对JVM的理解?Java8虚拟机和之前的变化更新?
    • 什么是OOM,什么是StackOverFlowError?怎么分析?
    • JVM的常用调优参数有哪些?
    • 内存快照如何抓取,怎么分析Dump文件?
    • 谈谈JVM中,类加载器的认识?

    一、JVM的位置

    JVM是运行在操作系统之上的,JVM可以理解为一个虚拟软件,JVM是用c语言写的,JRE(java运行环境)包含了JVM,操作系统的底层是硬件体系。

     

    二、JVM的体系结构

    .java文件编译成.class文件,通过类加载器将文件加载到JVM中,JVM也要给类加载器返回一些内容。

    运行时数据区与类加载器、执行引擎、本地方法区交互。

    运行时数据区:包括 方法区、Java栈、本地方法栈、堆、程序计数器。

    JVM的底层是操作系统,因此JVM与操作系统也相连,具体如下:

    •  因为有本地栈,因此操作系统的本地方法接口与硬件本地方法库相连接 ,与运行时数据区交互。
    • 操作系统层还有执行引擎。

    栈不需要垃圾回收机制,因为用完一个数据就出栈。程序计数器也没有垃圾。

    方法区是特殊的堆,JVM调优99%就是在堆内进行的。

    详图

     

    三、类加载器

    第三方插件一般在加载引擎上。

    作用:加载.class文件

    1.类加载器举例

    若加载一个Car.class,则Car.class通过类加载器找到Car类,进行实例化。

    这些实例化对象的名字在栈中(car1、car2、car3),而实例化的数据引用在堆内引用 (通过栈中存储的堆内地址寻找)

    package jvm;
    
    public class Car {
        public int age;
        public static void main(String[] args) {
            // 类是模板,对象是具体的
            Car car1 = new Car();
            Car car2 = new Car();
            Car car3 = new Car();
    
            car1.age = 1;
            car2.age = 2;
            car3.age = 3;
            System.out.println(car1);  //jvm.Car@15db9742
            System.out.println(car2);	//jvm.Car@6d06d69c
            System.out.println(car1.hashCode()); // 366712642
            System.out.println(car2.hashCode()); // 1829164700
            System.out.println(car3.hashCode()); // 2018699554
    
            //?是一个未知类型,是一个通配符泛型,这个类型是继承Car即可
            Class<? extends Car> aClass1 = car1.getClass();//getClass(),返回Class类型的对象
            Class<? extends Car> aClass2 = car2.getClass();
            Class<? extends Car> aClass3 = car3.getClass();
            
            System.out.println(aClass1);	//class jvm.Car
            System.out.println(aClass1.hashCode()); // 705927765
            System.out.println(aClass2.hashCode()); // 705927765
            System.out.println(aClass3.hashCode()); // 705927765
    
        }
    }
    

    运行结果:

     

    2. JVM中提供了三层的ClassLoader

    • Bootstrap classLoader(根加载器):主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。

    • ExtClassLoade(扩展类加载器:主要负责加载jre/lib/ext目录下的一些扩展的jar。

    • AppClassLoader(应用程序加载器):主要负责加载应用程序的主函数类。

     

    3. 双亲委派机制(重要)

    package java.lang;
    
    public class String {
        // 双亲委派机制:保证安全
        // 1.APP --> EXT --> BOOT(最终执行)
        
        public String toString() {
            return "Hello";
        }
    
        public static void main(String[] args) {
            String s = new String();
            System.out.println(s.getClass().getClassLoader());
            s.toString();
            /*
            错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
                public static void main(String[] args)
            否则 JavaFX 应用程序类必须扩展javafx.application.Application
             */
        }
        //明明main方法存在,却说找不到,原因在于双亲委派机制。调用时,加载的是JRE中的rt.jar包下的java.lang包中的String类
       
        /*
             双亲委派机制
        1.类加载器收到类加载的请求
        2.将这个请求向上委托给父类加载器去完成,一直向上委托,知道启动类加载器
        3.启动加载器检查是否能够加载当前这个类,能够加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
        4.重复步骤 3
       
        null : java调用不到~C、C++
       
         */
    }
    

    明明main方法存在,却说找不到,原因在于双亲委派机制。调用时,加载的是JRE中的java.lang包下的String类。

     

    3.1 工作原理

    双亲委派机制的工作原理

    (1)如果一个类加载器收到了类加载请求,它并不会自己先加载,而是把这个请求委托给父类的加载器去执行;

    (2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的引导类加载器;

    (3)如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制;

    (4)父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常;

    即从应用程序加载器一层一层向上询问,是否已经加载过,若未加载,再从顶层向下加载。即根加载器内找此类,若已加载则结束,若无此类,再在扩展类加载器中找,若存在则结束。若不存在,则在应用程序加载器中找,若存在则结束,若不存在返回null。

    java是在c++的基础上去掉了指针内存管理

    native是调用本地的方法库,此时java已经处理不了了。

     

    3.2.优点

    设计双亲委派机制的好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被根加载器加载过了(为什么?

    因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader,即java原先的String类),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。(防止人为的篡改)

    面试问题:

    • 为什么需要双亲委派机制?(也就是双亲委派的优点)

      ①双亲委派机制使得类加载出现层级,父类加载器加载过的类,子类加载器不会重复加载,可以防止类重复加载

      ②使得类的加载出现优先级,防止了核心API被篡改,提升了安全,所以越基础的类就会越上层进行加载,反而一般自己的写的类,就会在应用程序加载器(Application)直接加载。

    • 如何打破双亲委派?

      ①自定义类加载器,重写loadClass方法

      ②使用线程上下文类加载器

     

     四、沙箱安全机制(了解)

    1、什么是沙箱

    ​ 沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制远程代码对系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络等。不同级别的沙箱对这些资源访问的限制也可以不一样。

    ​ 所有的Java程序运行都可以指定沙箱,可以定制安全策略。

     

    2、组成沙箱的基本组件

    • 字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范(格式)。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类(核心类已经测试过了,不需要再校验)。
    • 类装载器(class loader):其中类装载器在3个方面对Java沙箱起作用
      • 它防止恶意代码去干涉善意的代码(核心类中的代码);
      • 它守护了被信任的类库边界;
      • 它将代码归入保护域,确定了代码可以进行哪些操作。

      虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。

      类装载器采用的机制是双亲委派模式。

    1. 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
    2. 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
    • 存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
    • 安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
    • 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
      • 安全提供者
      • 消息摘要
      • 数字签名
      • 加密
      • 鉴别

     

    五、Native (重要)

    package jvm;
    public class Demo {
        public static void main(String[] args) {
        	/* () -> {},其中 () 用来描述参数列表,
        	 * {} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。
    		
    			thread后面的括号是构造方法的括号,构造方法的参数是和runnable接口绑定的,
        	         所以lambda表达式符合该函数类型的接口
        	 */
            new Thread(()->{
            	
            }).start();
        }
    
        // native:凡是带了native 关键字的,说明java的作用范围达不到了,回去调用底层C语言的库
        // 会进入本地方法栈
        // 调用本地方法接口 JNI
        // JNI作用:扩展Java的使用,融合不同的编程语言为Java所用! 最初:C、C++
        // Java诞生的时候,C、C++ 横行,想要立足,必须要有调用C、C++ 的程序
        // 它在内存区域中专门开辟了一块标记区域:Native Method Stack,登记 native 方法
        // 在最终执行的时候,通过JNI 加载本地方法库中的方法。
    
        // Java程序驱动打印机,管理系统,掌握即可,在企业级应用中较为少见!
    
        private native void start0();
    
        // 调用其他接口:Socket、WebService、HTTP
    }
    

    线程的源码: 

     native:凡是带了native 关键字的,说明java的作用范围达不到了,回去调用底层C语言的库,会进入本地方法栈,调用本地方法接口 JNI

    本地方法接口JNI的作用:扩展Java的使用,融合不同的编程语言为Java所用! 最初:C、C++。

    Java诞生的时候,C、C++ 横行,想要立足,必须要有调用C、C++ 的程序。

    Java在内存区域中专门开辟了一块标记区域:本地方法栈Native Method Stack,登记 native 方法。

    在最终执行的时候,通过JNI 加载本地方法库中的方法。

    Java程序驱动打印机,管理系统,掌握即可,在企业级应用中较为少见!

    小结:

    Java在内存区域中专门开辟了一块标记区域——本地方法栈,用来登记native方法,凡是带了native关键字的,会进入到本地方法栈中,调用本地方法接口(JNI),在最终执行的时候,加载本地方法库中的方法通过JNI。

    它的具体做法是在本地方法栈中登记 native 方法,在执行引擎执行的时候加载本地方法库。 

     

    八、PC寄存器

    程序计数器:Program Counter Register

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

    因为程序计数器的存在,线程从标号1排到8顺序才不会乱.

     

    九、方法区

    可参考另一篇博文,帮助理解。https://blog.csdn.net/qq_40243295/article/details/111468616 中的1.3对象内存图

    Method Area 方法区

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

    静态变量、常量、类信息(构造方法、接口定义)、运行时常量池存在方法区中,但是实例变量存在堆内存(将地址传给方法区)中,和方法区无关。

    static, final, Class,常量池修饰的都在方法区。

     方法区是所有线程共享的内存,在java8以前是放在JVM内存中的,由永久代实现,受JVM内存大小参数的限制,在java8中移除了永久代的内容,方法区由元空间(Meta 

    Space)实现,并直接放到了本地内存中,不受JVM参数的限制(当然,如果物理内存被占满了,方法区也会报OOM),并且将原来放在方法区的字符串常量池和静态变量

    都转移到了Java堆中 。

    三种JVM:

    • Sun公司 HotSpot Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
    • BEA JRockit
    • IBM J9VM

    下面的学习内容都是针对于Sun公司 HotSpot 的JVM的。

     

    十、堆

    栈:先进后出,后进先出

    队列:先进先出(FIFO:first input first output)

    堆:堆内存,主管程序的运行,生命周期和线程同步;

    线程结束,栈内存也就释放了,对于栈来说不存在垃圾回收问题,一旦线程结束,栈就Over了

    1、堆里面存放什么

    堆:8大基本类型 + 对象的引用 + 实例的方法。

    堆内存中还要细分为三个区域:

    • 新生区(伊甸园区)young/new
    • 养老区 old
    • 永久区 perm

    堆包括:新生区(伊甸园区、幸存区)、养老区、永久区

    2、新生区

    新生区:类诞生和成长的地方,甚至死亡;

    新生区包括:伊甸园区和幸存区

    新生区:

    • 伊甸园区:所有对象都是在伊甸园区new出来的
    • 幸存区(0、1/from、to)

    伊甸园中经过轻量级之后还活着,移至幸存区(0、1),若伊甸园区和幸存区都满了,则进行一次重量级,如果此线程还活着则放入养老区。

    GC垃圾回收,主要在伊甸园区养老区。

    假设内存满了,OOM,堆内存不够!java.lang.OutOfMemoryError: Java heap space 

    JVM在进行GC时,并不是对这三个区域统一回收。大部分时候,回收都是新生代

    • 新生代
    • 幸存区(from,to)
    • 老年区

    GC两种类型:轻GC(普通的GC),重GC(全局GC)

    GC题目:

    • JVM的内存模型和分区?详细到每个区放什么?
    • 堆里面的分区有哪些?Eden,from,to,老年区,说说他们的特点!
    • GC的算法有哪些?标记清除法,标记压缩,复制算法,引用计数法,怎么用的?
    • 轻GC和重GC分别在什么时候发生?

     

    3、养老区

    从新生区存活下的,即未死亡的的来到了养老区。

    当新生代经历15次轻GC后还存在引用的,则被转移到养老区。

     

    4、永久区

    这个区域是常驻内存的。用来存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收!当关闭VM虚拟机就会释放这个区域的内存。

    出现OOM的原因:

    • 启动类加载了大量的第三方jar包;
    • Tomcat部署了太多的应用;
    • 大量动态生成的反射类等 不断的被加载;

    元空间:这个区域常驻内存的,用来存放jdk自身携带的class对象,interface元数据。

    常量池有字符串常量池运行时常量池

    • jdk1.6之前:永久代,运行时常量池和字符串常量池在方法区。
    • jdk1.7 :永久代,但是慢慢退化,去永久代,字符串常量池在堆中。
    • jdk1.8之后:无永久代,运行时常量池在元空间,字符串常量池在堆中。

    package jvm;
    
    /**
     * 元空间逻辑上存在,物理上不存在
     */
    public class Demo1 {
        public static void main(String[] args) {
            // 返回jvm试图使用的最大内存
            long max = Runtime.getRuntime().maxMemory();
            // 返回jvm的初始化内存
            long total = Runtime.getRuntime().totalMemory();
    
            System.out.println("max="+max+"字节\t"+(max/(1024*1024))+"MB");
            System.out.println("total="+total+"字节\t"+(total/(1024*1024))+"MB");
    
            //默认情况下,试图分配的最大内存是电脑内存的1/4,而初始化的内存是1/64
            // -Xms1024m -Xmx1024m -XX:+PrintGCDetails
        }
    }

    默认情况下,试图分配的最大内存是电脑内存的1/4,而初始化的内存是1/64。 

                             

    修改堆内存分配过程:

    输入: -Xms1024m -Xmx1024m -XX:+PrintGCDetails

     

    将内存设置改变后,运行结果: 

    新生区:305664k;养老区:699392k

    内存相加:305664+699392=1,005,056k,换算单位:1,005,056/1024 = 981.5MB,大小等于JVM分配的最大内存,所以说元空间逻辑上存在,物理上不存在

    VM options参数

    -Xms 设置初始化内存分配大小,默认1/64

    -Xmx 设置最大分配内存,默认1/4

    -XX:+PrintGCDetails 打印GC垃圾回收信息

    -XX:+HeapDumpOnOutOfMemoryError 生成oomDump文件

    -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

    -Xms1024m -Xmx1024m -XX:+PrintGCDetails

     

    十一、出现OOM(栈溢出)

    1. 尝试扩大堆内存去查看内存结果

      -Xms1024m -Xmx1024m -XX:+PrintGCDetails

    2. 若不行,分析内存,看一下是哪个地方出现了问题(专业工具)

      • 能够看到代码第几行出错:内存快照分析工具,MAT(eclipse),Jprofiler
      • Dubug,一行行分析代码!(不现实)

    MAT,Jprofiler作用:

    • 分析Dump内存文件,快速定位内存泄漏
    • 获得堆中的数据
    • 获得大的对象
    • ......
    import java.util.ArrayList;
    
    public class JprofilerTest {
        byte[] array = new byte[1*1024*1024];
    
        public static void main(String[] args) {
            ArrayList<JprofilerTest> list = new ArrayList<>();
            int count = 0;
            try {
                while(true){
                    list.add(new JprofilerTest());
                    count++;
                }
            }catch (Exception e){
                System.out.println("count="+count);
                e.printStackTrace();
            }
        }
    }

    在idea中设置VM options:右键——>   

    设置VM options -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError后,再次运行,控制台输出:

    出现堆溢出: 

     

    十二、GC的算法

    1. 引用计数法

    给对象的引用进行计数(统计),每当有一个地方引用它时,计数器就加1,当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能在被使用的,垃圾收集器将回收该对象使用的内存。

    客观的说,引用计数算法的实现简单,判定的效率很高,在大部分的情况下是一个不错的算法。

    默认标记次数 = 15次,当标记 = 0,gc直接回收
    对象被引用的话,就会 +1,然后放到 from或者to区,如果还是被频繁使用,那么会放入老年代中。

    但引用计数法java中现在已经很少能用到。

     

    2. 复制算法

    为解决效率问题,“复制”收集算法出现了。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块(to)。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

    优点:没有内存的碎片。“复制”使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

    它的主要缺点有两个:
            (1)效率问题:在对象存活率较高时,复制操作次数多,效率降低;
            (2)空间问题:浪费了内存空间,多了一半空间永远是空to,假设对象100%存活(极端情况);需要额外空间做分配担保(老年代)

    复制算法最佳使用场景:对象存活度较低的时候(即在新生区时使用复制算法)。

    from和to区是不停交换的,谁空谁是to。每次将一个幸存区的对象复制到另外一个幸存区,将一个幸存区腾空,命名为to。

     

    3. 标记清除

    3.1 标记清除

    标记清除

    • 缺点:两次扫描,严重浪费时间,会产生内存碎片
    • 优点:不需要额外的空间

     

    3.2 标记压缩

    对标记清除方法的再优化,对于标记清除的再压缩

    标记压缩

    缺点:较标记清除,又多了一个移动成本

     

    3.3 标记清除压缩

    先标记清除几次,然后再压缩。

     

    4. 算法总结

    内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)

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

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

    思考:难道没有最优算法吗?

    答案:没有,没有最好的算法,只有最合适的——>GC:分代收集算法

    年轻代:

    • 存活率低
    • 复制算法

    老年代:

    • 区域大,存活率高
    • 标记清除(内存碎片不是太多) + 标记压缩混合实现

    还是要多看书《深入理解JVM》,花时间去深究,多看面试题。

     

    十二、JMM

    学习方法:

    1.什么是JMM?(查百度百科)

    JMM:Java Memory Model的缩写

    2.它干嘛的?(博客)

    作用:缓存一致性协议,用于定义数据读写的规则。

    JMM定义了线程工作内存和主内存之间的抽象关系,线程之间的共享内存存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)

    解决共享对象可见性这个问题:voliate

    3.它该如何学习?(学jmm的本质为了解决什么问题,理论落地需要什么?)

    JMM:抽象的概念,理论

    voliate等等(理论落地)

    看面试题(总结思路)

    展开全文
  • 一个字体按钮类(WTL)

    2011-01-18 15:46:00
    另一个是button按钮的子类化,产生一个有立体感的字体(本站上的一个例子是用MFC做的CMyTextButton类,我把它移植到WTL中,封装成CFontButton类,在这里先感谢提供此代码的作者),不过只能显示英文字体。通过这些例子,...

      本文打算介绍两个内容:一个是WTL中非常好用的CDialogResize模板类,能让你拖动对话框改变大小(包括控件能自动适应对话框的改变);另一个是button按钮的子类化,产生一个有立体感的字体(本站上的一个例子是用MFC做的CMyTextButton类,我把它移植到WTL中,封装成CFontButton类,在这里先感谢提供此代码的作者),不过只能显示英文字体。通过这些例子,你可以看出WTL并没有什么神秘的地方。

      一. CDialogResize类的使用

      1.对话框继承的基类列中添加public CDialogResize<CDerive>

      本例中添加

     

    public CDialogResize<CMainDlg>

     

    2.添加宏

     

    BEGIN_DLGRESIZE_MAP(CDialogResize<>)
           DLGRESIZE_CONTROL(id, flags)
    END_DLGRESIZE_MAP()

     

      指定那个控件需要这种支持,其中id是控件ID,flags的值如下所示:

      DLSZ_SIZE_X = 0x00000001,

      DLSZ_SIZE_Y = 0x00000002,

      DLSZ_MOVE_X = 0x00000004,

      DLSZ_MOVE_Y = 0x00000008,

      DLSZ_REPAINT = 0x00000010.

      3.在OnInitDialog()中添加DlgResize_Init(),OK

      在atlframe.h中你可以找到DlgResize_Init()的定义,以前也有人用MFC写过类似的代码,有兴趣的话比较一下。

      最后别忘了在stdafx.h中添加相应的头文件,具体见源文件。

      二.CFontButton类的实现

      1.COwnerDraw和CCustomDraw

      这两个模板类都支持自画功能,他们两个的区别是CCustomDraw处理NM_CUSTOMDRAW,一般支持header,

      list view, rebar, toolbar, tooltip, trackbar 和 tree view 控件的重绘;COwnerDraw处理WM_DRAWITEM, WM_MEASUREITEM, WM_COMPAREITEM和 WM_DELETEITEM这四个消息,通常支持button, combo box, list box, list view control, 和 menu items

      的自绘。

      2. CCustomDraw 的消息映射

     

    template <class T> class COwnerDraw
    {
    public:
        BEGIN_MSG_MAP(COwnerDraw<T>)
        MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)
        MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem)
        MESSAGE_HANDLER(WM_COMPAREITEM, OnCompareItem)
        MESSAGE_HANDLER(WM_DELETEITEM, OnDeleteItem)
        ALT_MSG_MAP(1)
        MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
        MESSAGE_HANDLER(OCM_MEASUREITEM, OnMeasureItem)
        MESSAGE_HANDLER(OCM_COMPAREITEM, OnCompareItem)
        MESSAGE_HANDLER(OCM_DELETEITEM, OnDeleteItem)
        END_MSG_MAP()
    };

     

     有两种方法处理WM_DRAWITEM,你可以在控件的父窗口处理此消息,如下所示:

     

    class CXXXDlg : public COwnerDraw, ...
    {
      BEGIN_MSG_MAP(CSomeDlg)
        CHAIN_MSG_MAP(COwnerDraw<CXXXDlg>)
      END_MSG_MAP()
      void DrawItem ( LPDRAWITEMSTRUCT lpdis );
    };

     

     另一种方法是让控件自己处理此消息,如下所示,本例中采用的就是此种方法

     

    class CFontButtonImpl : public COwnerDraw<CFontButtonImpl>, ...
    {
      BEGIN_MSG_MAP(CSomeButtonImpl)
        CHAIN_MSG_MAP_ALT(COwnerDraw<CFontButtonImpl>, 1)
        DEFAULT_REFLECTION_HANDLER()
      END_MSG_MAP()
      void DrawItem ( LPDRAWITEMSTRUCT lpdis );
    };

     

     3. CControlWinTraits

     

    typedef  CWinTraits<WS_CHILD | WS_VISIBLE |
      WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0>  CControlWinTraits;

     

     想必大家一看就明白是什么意思。

      4. CFontButton类的使用

      在WTL工程中加入文件FontButton.h,然后添加变量

      

    CFontButtonCtrl m_FontButton;

     

      在OnInitDialog( )中添加

    m_FontButton.SubclassWindow(GetDlgItem(IDC_BUTTON_FONT1));

     

    单击进入查看本文地址:【伊甸网】http://www.edenw.com/tech/devdeloper/vc/2010-01-23/761.html

    展开全文
  • 但是 —— 你看见了“但是”,就知道准没好事 —— 有一个问题,这样的市场是架在行政监督之上的,意味着要么永远做不大,要么做大了被行政力量盯上,并被这股力量利用唯一垄断且合法的手段强行划分市场。...
    打车APP的问题在于,这样的市场是架在行政监督之上的

      打车APP的问题在于,这样的市场是架在行政监督之上的

      吴思扬

    互联网创业已经进入了“量子”时代 —— 天文级数量,随机而多态,复杂环境,不遵循传统规律。

    创 业项目,砍掉99%初衷不靠谱的,再砍掉99%没资源的,再砍掉99%人不对路的,剩下这千分之一就算有戏。有戏的项目,如果遇上合适的机遇,恰 到 好处的资金注入,再耐得住寂寞,几万个项目总有几个能转为良性循环。能良性循环,却也不一定能满足投资者的大胃口,项目本身如果缺乏泡沫,多半不被看重, 高溢价进入高溢价退出的项目,投资价值最大。

    最后幸存的项目,还需要面对立项以来最没有胜算的挑战:行政力量。

    揭去打车应用的包装,本质上它所建立的,只是一种利润分成渠道:消费者用钱换时间,司机群体用装机量换生意门路,产品方用运维成本换大数据。如果只看产品逻辑的内循环,这将是一个近似于无懈可击的模式。

    但是 —— 你看见了“但是”,就知道准没好事 —— 有一个问题,这样的市场是架在行政监督之上的,意味着要么永远做不大,要么做大了被行政力量盯上,并被这股力量利用唯一垄断且合法的手段强行划分市场。

    来自北京的新闻给各类打车应用上了一课:北京市发改委谈出租车调价听证方案:还会出台配套措施

    这则新闻可以视为一个信号,随着互联网创业的影响力逐步扩大,当其波及传统行业自身改造时,传统行业必然会给出不可预料的反应,且对于创业者来说,这些反应基本属于利空。延展到与行政资源相关的其他领域,会发现不少有趣的事情。

     延展一:《财政部 民政部 国家体育总局 关于彩票机构利用互联网销售彩票 有关问题的通知》

    这 个通知直接掐死了一部分移动端产品,也极大地提高了竞彩类产品门槛。而做体育类的产品,以我的分析,最终都会走向两条路:竞彩,厂商。竞彩可以玩 彩 票,可以玩地下赌博,但在大陆能合法运营的形态极少,走向厂商的话,要么做成数据商,要么做成体育品牌的附属产品。而这个政策无异于给了众多体育类产品创 业人一记闷棍,现在能玩得转的,除了局里有人,就是兜里有钱。

     延展二:交通杭州APP推出2个月 市交通局征10人体验

    据 我所知,国内做停车app的团队不下30家,产品已面市的,至少上十款。但这都是浮云,因为停车场数据一直是个难题,单纯提供停车场地点的产品, 是 在卖萌,必须要有实时或准实时的车位信息,才真 正帮得上车主的忙。创业团队也好,实力公司也好,一家家去找停车场去谈数据接入?难度实在高。可交管部门一纸令下,这些私营停车场都必须合作,足见行政力 量威力。

      延展三:交通广播APP是您的出行好伴侣

    曾经用过几款产品,它们与交管部 门谈成了数据对接,本以为有一番大作为,可当时看到了这则新闻,顿觉浮云。我下一个论断,在大陆司机圈内,没有任何 产 品在公信力上能挑战本地交通台,如果碰巧有一款产品能取代或与交通台并行,背后一定有政府背景。交通监管摄像头遍布,但管理部门不会将这些资源出让给一家 缺乏资 质、难以信任的私营企业。更多的情况将会是有限度开放,而且缺少部分关键信息。但作为政府宣传阵地之一的本地交通台,天生就与交管部门合作,无论是从使用 场景还是从公信力上看,都比手机App更靠谱。

     延展四:广州地铁官方App

    这可能是 行政力量与互联网的最优结合形态。掌握了城市某一类行政资源的企事业单位,内部组建互联网团队,利用天然的数据优势和政策优势,做良心产 品, 同时由于没有太多的生存负担,这类产品将可能整合出小团队无法逾越的功能壁垒。这种模式虽好,但有一个前提:掌握行政资源的企事业单位,中高层能想明白互 联网的玩法。不过这也并不致命,完全可以冷眼旁观,等小团队前仆后继将市场培育好后,轻松介入,这一切只需要一个契机。

    这些行政资源与互联网产品结合的案例摆在眼前,打车类产品的命运其实不难推演。

    各城市的出租车公司,由于其经营内容本身具有“公共资源”的特征,最终都会受市政牵制或管理。而打车产品的命脉在于行业协会,一旦此处政策收口、提高数据门槛或收取高额准入费用,创业产品将被直接打成废品状态。

    目前打车产品在做小白鼠和排雷兵,一旦加价打车的市场培育成型,或催生出其他形态的盈利方式,除非与市政合作、与出租车行业协会联手,作为独立公司,将无法在这样的压力下存活。

    目前各互联网巨头投资打车应用,也并不代表他们看好“打车”这一领域,更有可能的是作为防御性投资,一旦势头不好,不会因过于牵连而受影响,势头若好,便可以顺藤摸瓜。

    由 此看,打车这个市场,行政力量、行业力量、互联网巨头都在虎视眈眈、各揣鬼胎,而身在暴风中心的创业团队,却显得孤立无援、前途堪忧。不得不说, 这 个领域实在不适合互联网创业,行政优于渠道,渠道优于产品,而 UX/UI 和技术能力早已不在讨论之列。如此凶险,根源在于,这个领域内的资源和用户,无论从哪个角度看,都不受产品制约,即便有盈利模式,也最终还是会成为他人的 点金石。

    无觅相关文章插件,快速提升流量

     

    想第一时间获取 移动互联网行业新鲜资讯和深度商业分析,请在微信公众账号中搜索“ 伊甸网”或者“edenw_com”,或用手机扫描下方二维码,即可获得伊甸网每日精华内容推送和最优搜索体验,并参与编辑互动!

    转载于:https://my.oschina.net/edenw/blog/130138

    展开全文
  • 次线上游戏卡死的解决历程

    千次阅读 2017-09-08 14:23:39
    事故的发生详细过程故事是发生在几月前的线上真实案例,我将在本文中以故事形式为大家还原这次解决游戏卡死的经历过程,其中有很多线上实战经验和技巧都值得分享借鉴的,也有作者自创的处理线上问题“四部曲”–望...

    GitChat 作者:杨彪
    原文:一次线上游戏卡死的解决历程
    关注微信公众号:GitChat 技术杂谈 ,一本正经的讲技术

    【不要错过文末活动】

    事故的发生详细过程

    故事是发生在几个月前的线上真实案例,我将在本文中以故事形式为大家还原这次解决游戏卡死的经历过程,其中有很多线上实战经验和技巧都值得分享借鉴的,也有作者自创的处理线上问题“四部曲”–望问闻切,还有最经典的“甩锅”秘诀。不管白猫黑猫,能立马解决线上问题的就是好猫,线上问题实战经验最重要。下来就让我先来回顾下这次事故发生的背景吧。

    公司的游戏获得了Google Play的最佳新游推荐位展示,这代表着公司游戏可以在Google Play首页持续一周的全球推荐。如果对Google Play还不了解的小伙伴们可以看看下图,展示了Google Play推荐位的效果:

    Google Play 推荐位

    就是在这样一个重大利好消息推动下,项目研发组紧急加班加点地赶制进度和进行游戏压力测试(以后有机会详细写篇游戏压力测试机器人实现方案),最后内网测试环境(Testing Environment)和预生产环境(Staging Environment)一切都测试正常,随时等待更新线上正式坏境了。

    像以往一样,游戏发布了停服更新公告(由于新增加了联盟战和几个大的活动,担心不停服更新有问题),执行完停服、备份、更新等一系列自动化流程后,服务器状态变为“更新完毕,白名单可进入”状态,然后通知QA进行上线前的最后一次生产环境(Production Environment)测试。整个项目的同学和QA同学以白名单身份在线上生产环境测试了近半个小时,没有任何bug和异常,我们就信心满满的准备开服了。

    游戏对外开放后,我们像往常一样边观察边继续做着新的工作,突然公司运营同学过来说游戏怎么感觉好卡,我们的第一反应是你网卡了吧(因为游戏服务器在国外,中间有一道不可逾越的qiang),我也没太在意还是继续做着别的事情,后来QA同学也说游戏好卡啊,我自己也登陆游戏试了下,确实挺卡,每一次操作都要等待好久,不过到现在我还是没有意识到服务器卡了,只是让运维同学查看游戏服的log有没有报错,而日志显示似乎一切都正常(后面会解释为什么日志还正常地输出)。

    慢慢地游戏内的聊天中开始有玩家反馈这次更新后游戏太卡,而且反馈的用户越来越多,我这才意识到问题的严重性了,游戏服肯定哪里操作太慢反应迟钝了(之前可能因为游戏公测到现在大半年还没出现过事故,所以有点掉以轻心了)。

    公司BOSS也过来问怎么回事,正值Google Play推荐导量的时期,公司上下非常重视。当然我知道越是面对大问题,有经验的人就越要冷静,我直觉得给BOSS说:“服务器有点卡了,小问题,马上就能弄好的,别着急”。其实当时我心里也没底,不知道问题在哪,不过根据自己以往经验和实践操作,只要按照“四步曲”流程化的执行一遍,肯定能找到点线索和眉目的。

    更多的线上应急和技术攻关可以参照我和朋友合著的《分布式服务架构:原理、设计与实战》一书中的第六章“Java服务的线上应急和技术攻关”,该章中介绍了海恩法则和墨菲定律,以及线上应急目标、原则和方法,同时提供了大量的Linux和JVM命令,也有很多平时工作中常用的自定义脚步文件和实际案例。

    接下来我们一起,一步步地解决游戏卡死的问题,文章中很多截图只是本次文章演示说明,不是当时的现场截图,不过我会说明清楚尽量还原线上真实过程。

    事故的处理过程还原

    解决线上问题的“四部曲”

    • :就是观察的意思,出了问题最重要一点就是观察线上问题发生的规律,切忌有病乱投医,一上来就先各种偿试各种改的。除了观察现象外,我们还要观察各种日志、监控和报警系统,具体如何搭建“大数据日志监控系统”请参照作者书的第四章。

    • :就是问清楚现在问题发生的情况,这个很重要,后面会重点介绍具体需要问清楚的哪些问题。

    • :就是认真听取别人的意见,有时线上出了问题,我们大多数心里还是比较抵触别人说这说那的,不过在这种情况下,我们更应该多听,找到可能引起问题的情况或有关的事情,同时也为后面的“甩锅”技巧打开思路。

    • :就是动手实践验证了,通过前面的观察、询问问题,我们心中应该能有些假设和猜测的线索了,这时候就需要动手在测试环境或预生产环境上进一步验证我们的假设是否成立了。

    下面这张图概括的介绍了“望问闻切”各阶段需要关心和注重的事情:

    四部曲

    前面通过对“四部曲”的介绍,大家可能会觉得很抽象,不过它是我们解决线上问题的指导方针、核心思想,那我们在实际项目中又是如何“望问闻切”的呢?

    首先是如何发现问题

    发现问题通常通过自动化的监控和报警系统来实现,线上游戏服搭建了一个完善、有效的日志中心、监控和报警系统,通常我们会对系统层面、应用层面和数据库层面进行监控。

    对系统层面的监控包括对系统的CPU利用率、系统负载、内存使用情况、网络I/O负载、磁盘负载、I/O 等待、交换区的使用、线程数及打开的文件句柄数等进行监控,一旦超出阈值, 就需要报警。对应用层面的监控包括对服务接口的响应时间、吞吐量、调用频次、接口成功率及接口的波动率等进行监控。

    对资源层的监控包括对数据库、缓存和消息队列的监控。我们通常会对数据库的负载、慢 SQL、连接数等进行监控;对缓存的连接数、占用内存、吞吐量、响应时间等进行监控;以及对消息队列的响应时间、吞吐量、负载、积压情况等进行监控。

    其次是如何定位问题

    定位问题,首先要根据经验来分析,如果应急团队中有人对相应的问题有经验,并确定能够通过某种手段进行恢复,则应该第一时间恢复,同时保留现场,然后定位问题。

    在应急人员定位过程中需要与业务负责人、技术负责人、核心技术开发人员、技术专家、
    架构师、运营和运维人员一起,对产生问题的原因进行快速分析。在分析过程中要先考虑系统最近发生的变化,需要考虑如下问题。

    • 问题系统最近是否进行了上线?

    • 依赖的基础平台和资源是否进行了上线或者升级?

    • 依赖的系统最近是否进行了上线?

    • 运营是否在系统里面做过运营变更?

    • 网络是否有波动?

    • 最近的业务是否上量?

    • 服务的使用方是否有促销活动?

    然后解决问题

    解决问题的阶段有时在应急处理中,有时在应急处理后。在理想情况下,每个系统会对各种严重情况设计止损和降级开关,因此,在发生严重问题时先使用止损策略,在恢复问题后再定位和解决问题。解决问题要以定位问题为基础,必须清晰地定位问题产生的根本原因,再提出解决问题的有效方案,切记在没有明确原因之前,不要使用各种可能的方法来尝试修复问题,这样可能还没有解决这个问题又引出另一个问题。

    最后消除造成的影响

    在解决问题时,某个问题可能还没被解决就已恢复,无论在哪种情况下都需要消除问题产生的影响。

    • 技术人员在应急过程中对系统做的临时性改变,后证明是无效的,则要尝试恢复到原来的状态。

    • 技术人员在应急过程中对系统进行的降级开关的操作,在事后需要恢复。

    • 运营人员在应急过程中对系统做的特殊设置如某些流量路由的开关,需要恢复。

    • 对使用方或者用户造成的问题,尽量采取补偿的策略进行修复,在极端情况下需要一一核实。

    • 对外由专门的客服团队整理话术统一对外宣布发生故障的原因并安抚用户,话术尽量贴近客观事实,并从用户的角度出发。

    当我们详细地了解了如何发现问题、定位问题、解决问题和消除造成的影响后,接下来让我们看下本次解决线上游戏卡死过程中是如何具体的应用的。

    排查游戏卡死的过程

    第一步,找运维看日志

    如果日志监控系统中有报错,谢天谢地,很好定位问题,我们只需要根据日志报错的堆栈信息来解决。如果日志监控系统中没有任何异常信息,那么接下来就得开始最重要的保存现场了。

    第二步,保存现场并恢复服务

    日志系统中找不到任何线索的情况下,我们需要赶紧保存现场快照,并尽快恢复游戏服务,以达到最大程度止损的目的。

    通常JVM中保存现场快照分为两种:

    • 保存当前运行线程快照。

    • 保存JVM内存堆栈快照。其方法如下:

    1. 保存当前运行线程快照,可以使用jstack [pid]命令实现,通常情况下需要保存三份不同时刻的线程快照,时间间隔在1-2分钟。

    2. 保存JVM内存堆栈快照,可以使用jmap –heapjmap –histojmap -dump:format=b,file=xxx.hprof等命令实现。

    快速恢复服务的常用方法:

    1. 隔离出现问题的服务,使其退出线上服务,便于后续的分析处理。

    2. 偿试快速重启服务,第一时间恢复系统,而不是彻底解决问题。

    3. 对服务降级处理,只使用少量的请求来重现问题,以便我们可以全程跟踪观察,因为之前可能没太注意这个问题是如何发生的。

    通过上面一系列的操作后,保存好现场环境、快照和日志后,我们就需要通过接下来的具体分析来定位问题了。

    第三步,分析日志定位问题

    这一步是最关键的,也是需要有很多实战经验的,接下来我将一步步还原当时解决问题的具体操作步聚。

    诊断服务问题,就像比医生给病人看病一样,需要先查看一下病人的脸色如何、摸一摸有没有发烧、或再听听心脏的跳动情况等等。同样的道理,我们需要先查看服务器的“当前症状”,才能进一步对症下药。

    • 首先使用top命令查看服务器负载状况

      top命令

    load average一共有三个平均值:1分钟系统负荷、5分钟系统负荷,15分钟系统负荷。哪我们应该参考哪个值?

    如果只有1分钟的系统负荷大于1.0,其他两个时间段都小于1.0,这表明只是暂时现象,问题不大。
    如果15分钟内,平均系统负荷大于1.0,表明问题持续存在,不是暂时现象。所以,你应该主要观察”15分钟系统负荷”,将它作为服务器正常运行的指标。

    说明:我们当时服务器负载显示并不高,所以当时第一反应就排除了承载压力的问题。

    • 接下来再使用top命令+1查看CPU的使用情况

      top命令

    我们主要关注红框中指标,它表示当前cpu空闲情况,而其它各指标具体含义如下:

    0.7%us:用户态进程占用CPU时间百分比,不包含renice值为负的任务占用的CPU的时间。

    0.0%sy:内核占用CPU时间百分比。

    0.0%ni:改变过优先级的进程占用CPU的百分比。

    99.3%id:空闲CPU时间百分比。

    0.0%wa:等待I/O的CPU时间百分比。

    0.0%hi:CPU硬中断时间百分比。

    0.0%si:CPU软中断时间百分比。

    说明:我们线上服务器为8核16G的配置,当时只有一个cpu显示繁忙,id(空闲时间百分比)为50%左右,其余显示90%多。从这里看似乎没有什么太大的问题。

    既然cpu负载和使用都没太大问题,那是什么卡住了服务呢?直觉告诉我,可能是线程死锁或等待了什么耗时的操作,我们接下来就来查看线程的使用情况。不过在查看线程使用情况之前,我们首先看看JVM有没有出现内存泄漏(即OOM问题,我的书中有介绍一个实际OOM的案例),因为如果JVM大量的出现FGC也会造成用户线程卡住服务变慢的情况。

    • 使用jstat –gcutil pid查看堆中各个内存区域的变化以及GC的工作状态

      jstat命令

    S0:幸存1区当前使用比例

    S1:幸存2区当前使用比例

    E:伊甸园区使用比例

    O:老年代使用比例

    M:元数据区使用比例

    CCS:压缩使用比例

    YGC:年轻代垃圾回收次数

    FGC:老年代垃圾回收次数

    FGCT:老年代垃圾回收消耗时间

    GCT:垃圾回收消耗总时间

    说明:当时服务也没有出现大量的FGC情况,所以排除了有OOM导致的用户线程卡死。

    • 接下来使用top命令+H查看线程的使用情况

    top命令

    PID:进程的ID

    USER:进程所有者

    PR:进程的优先级别,越小越优先被执行

    NInice:值

    VIRT:进程占用的虚拟内存

    RES:进程占用的物理内存

    SHR:进程使用的共享内存

    S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数

    %CPU:进程占用CPU的使用率

    %MEM:进程使用的物理内存和总内存的百分比

    TIME+:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。

    COMMAND:进程启动命令名称

    说明:通过查看线程%CPU指标,明显能看到某个java线程执行非常占用CPU,因此断定该线程当时出现了问题。那么我们接下来如何找到这个线程当时在干嘛呢?请看以下三步聚。(图片只是示意图,不是当时线上截图)

    • 使用jstack pid打印进程中线程堆栈信息,我们可以使用如下三步找出最繁忙的线程信息。

    查看进程中各线程占用cpu状态, 选出最繁忙的线程id,使用命令top -Hp pid

    jstack命令

    把线程id转成16进制,使用命令printf “%x\n”{线程id}

    jstack命令

    打印当前线程运行的堆栈信息,查找线程id为0x766B的线程堆栈信息

    jstack命令

    说明:线上通过打印繁忙线程,查看线程的执行堆栈,并没有找到被卡住的业务代码,每次都是执行成功的。当时就非常纳闷,为什么一直只是这一个线程在不停地消耗着CPU,突然一个编程的小技巧帮我找到了问题的罪魁祸首——线程中任务分配不均导致的服务响应变慢。

    小技巧:为不同的业务线程自定义名称,比如打印日志的线程为log_xxx,接收消息请求的线程为msg_xxx,游戏业务线程为game_xxx等。java中具体如何为线程命令如下图所示:

    threadname

    • 罪魁祸首——分布式唯一ID生成器

    wenti

    通过上面红框中的代码我们可以看到,线程任务的分配规则是通过用户的uuid模上线程池的长度,这样实现的目的是想让同一个用户的所有请求操作都分配到同一个线程中去完成(线程亲和性),这样的实现是为了从用户角度保证线程的安全性,不会出现多线程下数据的不一致性。

    而问题就出现在这个uuid取模上了,我们使用的是Twitter的分布式自增ID算法snowflake,而它生成的所有id刚好与我设置的线程池大小64取模后为0(具体原因不明),导致所有用户的所有请求全部分配到了一个线程中排队执行了。这也是为什么在查看线程堆栈信息时感觉都在正常执行,而打印的所有线程中只看到编号为0的线程在执行,其它都空闲等待。

    说明:此功能实现是在上线前两天,运营同学告诉说,有玩家反馈前一刻领取到的钻石在下一刻莫名消失了,我的第一反应肯定是多线程造成的,所以就临时采取了这种线程亲和方式统一解决了线程安全的问题。现在找到了问题产生的原因,接下来看是如何解决的。

    • 使用MurmurHash散列下解决ID生成不均匀的问题

    jiejue

    第四步,Hotfix后继续观察情况

    在测试环境或预生产环境修改测试后,如果问题不能再复现了,可以根据公司的Hotfix流程进行线上bug更新,并继续观察。如果一切都正常后,需要消除之前可能造成的影响。

    一键查看最繁忙线程堆栈脚本

    此命令通过结合Linux操作系统的ps命令和JVM自带的jstack命令,来查找Java进程内CPU利用率最高的线程,一般适用于服务器负载较高的场景,并需要快速定位负载高的成因。

    此脚本最初来自于互联网,后来为了让其在不同的类UNIX环境下运行,所以我做了一些修改,该命令在我每一次定位负载问题时都起到了重要作用。

    命令格式:

    ./show-busiest-java-threads -p 进程号 -c 显示条数
    ./show-busiest-java-threads -h

    使用示例:

    ./show-busiest-java-threads -p 30054 -c 3

    示例输出:

    findtop

    脚本源码见《分布式服务架构:原理、设计与实战》书中241页,更多服务化治理脚本请参照书中的第六章“Java服务的线上应急和技术攻关”。

    对本次线上事故的总结

    在技术方面,线上问题大致分为以下三类。

    CPU繁忙型

    • 线程中出现死循环、线程阻塞,JVM中频繁的垃圾回收,或者线程上下文切换导致。

    • 常用命令topjstack [pid]Btrace等工具排查解决。

    内存溢出型

    • 堆外内存: JNI的调用或NIO中的DirectByteBuffer等使用不当造成的。

    • 堆内内存:程序中创建的大对象、全局集合、缓存、 ClassLoader加载的类或大量的线程消耗等容易引起。

    • 常用的命令有jmap –heapjmap –histojmap -dump:format=b,file=xxx.hprof等查看JVM内存情况的。

    IO读写型

    • 文件IO:可以使用命令vmstatlsof –c -p pid等。

    • 网络IO: 可以使用命令netstat –anptcpdump -i eth0 ‘dst host 239.33.24.212’ -w raw.pcap和wireshark工具等。

    以上只是简单的介绍了下相关问题分类和常用命令工具,由于篇幅有限更多内容请参照《分布式服务架构:原理、设计与实战》书中“线上应急和技术攻关”一章,详细介绍了各种情况下技术命令的使用。

    在制度方面的应急处理和响应保障

    制定事故的种类和级别

    • S级事故,核心业务重要功能不可用且大面积影响用户,响应时间:立即。

    • A级事故,核心业务重要功能不可用,但影响用户有限;周边业务功能不可用且大面积影响用户体验,响应时间:小于15分钟。

    • B级事故,周边业务功能不可用,轻微影响用户体验,响应时间:小于4小时。

    说明:每个公司定义的事故种类和级别都不一样,具体情况具体分析,只要公司有了统一化的标准,当我们遇到线上问题时才不会显的杂乱无章,知道事情的轻重缓急,以及如何处理和什么时候处理。

    对待事故的态度

    • 保存现场并减少损失,第一时间恢复服务,减少线上损失,保存好现在所有信息用于问题分析定位。

    • 积极主动的解决问题,线上问题第一时间解决,也是展现个人能力的最佳时机。

    • 主动承担部分责任,承担自己能承担的责任,毕竟事故涉及KPI考核等问题,有时也需要混淆问题原因,拒绝老实人背锅。

    • 不要轻信经验,线上无小事,而大多引起线上事故的问题一般都是小问题,所以一定不要轻信经验,每一项改动都必须经过测试。

    说明:当线上出现问题后,大多数人第一反应可能是“这不关我事,我写的东西没问题”,面对线上问题不要怕承担责任,反而正是我们表现个人能力的最好时机。平时大家可能做了非常多的工作,勤勤恳恳的努力奉献着,最后BOSS连你的名字可能都没记住,尴尬!!但是一旦线上遇到问题,可能直接就造成很大的经济损失,全项目组甚至全公司都在关注的时候,你勇于站出来完美的解决了该问题,收获的成就会是相当大的。当然在这个过程中,我们也要学会“合理地甩锅”,具体的“甩锅”请听我直播。


    实录:《杨彪:线上游戏卡死问题实战解析》


    【GitChat达人课】

    1. 前端恶棍 · 大漠穷秋 :《Angular 初学者快速上手教程
    2. Python 中文社区联合创始人 · Zoom.Quiet :《GitQ: GitHub 入味儿
    3. 前端颜值担当 · 余博伦:《如何从零学习 React 技术栈
    4. GA 最早期使用者 · GordonChoi:《GA 电商数据分析实践课
    5. 技术总监及合伙人 · 杨彪:《Gradle 从入门到实战
    6. 混元霹雳手 · 江湖前端:《Vue 组件通信全揭秘
    7. 知名互联网公司安卓工程师 · 张拭心:《安卓工程师跳槽面试全指南

    这里写图片描述

    展开全文
  • Internet Introduction Author : shiyi001 && 伊甸一点 本文目录 ...亲爱的科学家们研究了很久很久,也没有得出一个规范化的定义呢~不过我们有一个简单的定义(●'◡'●):一些互相连接的...
  • JVM堆内存内容整理

    2020-06-22 22:46:48
    在jdk1.7之前,堆内存逻辑上分为新生区,老年区,永久代,1.8之后将永久代称之为元...新诞生的对象在伊甸园区,然后到s0或者s1,s0和s1只能有一个有内容另一个必定为空,再之后进入老年区。OOM异常就是堆内存溢出。 ...
  • JVM(七)

    2020-01-09 13:59:38
    复制算法的基本思想是将内存存在两块,每次只用其中块,当这块内存用完,就将活着的对象复制到另一 块上面。复制算法不会产生内存碎片。 优点:没碎片 缺点:消耗空间 伊甸园区 from区 to区 第次:伊甸园区里...
  • 方法区是线程间共享的,当两个线程同时需要加载一个类型时,只有一个类会请求ClassLoader加载,另一个线程会等待。 2.heap 堆区. 虚拟机中用于存放对象与数组实例的地方,垃圾回收的主要区域就是这里(还...
  • 章 Java开发中通用的方法和准则 建议1:不要在常量和变量中出现易混淆的字母; (i、l、1;o、0等)。 建议2:莫让常量蜕变成变量; (代码运行工程中不要改变常量值)。 建议3:三元操作符的类型务必...
  • 为啥会有一个Eden两个Survivor呢?因为采用复制清理算法,很耗内存资源,如果有两块,是不是第一块survivor满之后,将它和Eden存活对象复制到一块,清空第一块。是不是每次都有一块Survivor为空对吧,效率会比较高...
  • JVM笔记

    2020-08-15 20:36:17
    JVM分为两块区域,一个是线程共享的方法区和堆,另一个是线程独享的本地方法栈,程序计数器和Java栈。 1.1. 堆结构 Java堆内存位于JVM的线程共享区,内部分为新生代和老年代,在新生代中又分为伊甸园区,幸存零区,...
  • 复制:当伊甸区满的时候会触发第次gc,把还活着的对象拷贝到from区,当eden区再次触发gc的时候会扫描eden和from两区进行垃圾回收,经过这次,回收还存活的对象进入to区或者老年代(年龄>=15)。同时把这些对象...
  • 堆的概述一般来说:一个Java程序的运行对应一个进程;一个进程对应着一个JVM实例(JVM的启动由引导类加载器加载启动),同时也对应着多个线程;一个JVM实例拥有一个运行时数据区(Runt...
  • 阿里Java面经大全(整合版)

    万次阅读 多人点赞 2018-08-03 16:10:12
    1.上来问我项目用的框架,然后问我springmvc里面有的参数的设定,问的是细节,然后问我如果传的多个值是一个对象的属性,问我如何处理,我说直接在后端接收为对象就行了,然后突然问我http怎么传对象,这里有点不...
  • 垃圾回收是种回收无用内存空间并使其对未来实例可用的过程。 Eden(伊甸)区:当对象被实例化,首先会被放入Eden区 Servivor(存活区)区:当Eden区被填满时会调用年轻代GC(Minor GC),检查仍然存活的会被放入...
  • 新生代又划分为伊甸园区和幸存区,一个对象刚创建的时候是放到伊甸园区,在伊甸园区经过一次扫描之后,如果这个对象已经不被引用,则释放内存;如果依然被引用,则挪到新生代的幸存区中;幸存区的扫描频率会略低于...
  • 对象首先分配在伊甸园区Eden 新生代空间不足时,触发Minor GC,伊甸园和From存活的对象使用Copy算法复制到To区,存活对象加1并交换From区和To区 Minor GC会触发STW(Stop The World),暂停其他的用户线程,等待垃圾...
  • ————— 第二天 —————————————————下面我们起来研究这三问题。问题1:哪些是需要回收的?首先我们需要知道如何哪些垃圾需要回收?判断对象是否需要回收有两种算法。种...
  • python,.Net,Java的垃圾回收机制

    千次阅读 2016-08-26 16:39:00
    华电北风吹 天津大学计算机学院 日期:2016-08-26一、python的垃圾回收机制 1、引用计数 当一个对象的引用被创建或者复制时,...将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”
  • JVM1. 说一下 JVM 的主要组成部分?2. 谈谈对运行时数据区的理解?3. 谈谈对内存泄漏的理解?4. JMM是什么?5. 为什么要学习Jvm?6. 什么是栈帧?7. Stop-The-World8....[3] JVM如何判定一个对象是否应该被回
  • 【垃圾回收专题>】

    2020-08-05 10:40:29
    [3] JVM如何判定一个对象是否应该被回收?[4] 在java中为什么不推荐使用finalize[4] 详细说下四种引用?[5] 常用的垃圾收集算法有哪些?[4] 分代收集的理论支撑?[6] 常用的垃圾收集器(内部使用垃圾收集算法)有哪些...
  • 对象优先放在伊甸园区中,当伊甸园区放满之后,JVM会做minor GC(垃圾收集); Minor GC: 如何寻找垃圾对象? 可达性分析算法: 找到非垃圾对象之后将其移到Survivor区,然后清理到伊甸园区的垃圾对象; Java对象...
  • 从本小节开始,我们将用三小节来交流学习JVM内存机制相关的技术原理。 JVM内存机制是Java面试中的重中之重,面试中的绝对热点。作为名优秀的Java开发工程师,日常工作中必不可少的要与JVM打交道,我们必须考虑到...
  • 前言 在JVM性能调优时有三组件: 堆大小调整(内存最够大的话,尽量搞大点) 垃圾收集器调整 ...通常,在调优Java应用程序时,重要是以下两主要目标之: 响应性:应用程序或系统对请求的...
  • JVM 垃圾回收算法

    千次阅读 2020-05-09 11:07:35
    标记-清除算法将垃圾回收分为两阶段:标记阶段和清除阶段。 在标记阶段首先通过根节点(GC Roots),标记所有从根节点开始的对象,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象...
  • 提升的时候,存活区的对象不再是复制到另一个存活区,而是迁移到老年代,并在老年代一直驻留,直到变为不可达对象。 为了确定一个对象是否“足够老”,可以被提升(Promotion)到老年代,GC 模块跟踪记录每个存活区...
  • 因为新生代的GC采用的是复制算法,每次只会用到一个幸存区,当一个幸存区满了的时候,把还是活的对象复制到个幸存区,上个直接清空。这样做就不会产生内存碎片了。 tenured generation 就表示老年代。 compacting ...
  • ①首先说一下,GC里边在JVM当中是使用的ROOT算法,ROOT算法,什么称作为ROOT呢,就是说类的静态成员,静态成员就是static修饰的那种,是“根”的一个,根还包括方法中的成员变量,只有成员或对象不挂在根上,GC的...
  • java的垃圾回收

    2015-08-20 10:41:44
    jvm的垃圾回收是个老生常谈的问题,在这里,我会从以下一个方面来和大家聊聊垃圾回收。 1 在哪里收垃圾? 2 哪些内容可认为是垃圾? 3 怎么回收垃圾? 4 gc报告的阅读 在哪里收垃圾 这里,我建议大家先读一下拙作: java...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,574
精华内容 629
关键字:

另一个伊甸