分布式java应用学习笔记三

深入理解JVM

classLoader加载.class完成后,有两种执行方式

 解析执行 

 编译机器码执行: client server两种模式

Java代码执行机制

  编译为class文件

  Java源码编译机制

  

A  分析和输入的符号表(parse and enter)

Pase  词法和说法分析

Enter  符号输入到符号表

处理注解(annotation processing)

语义分析和生成class文件(analyse and generate)

类加载机制:

 装载(load) 全限定名+类加载器

 链接(link)   

 初始化(initialize):

             调用了new

             反射调用类中的方法

             子类调用了初始化

             Jvm启动过程中指定的初始化类 

类加载器有四种:

          Bootstrap classLoader 

          ExtensionClassLoader  加载扩展功能的一些jar

          SystemClass Loader  加载启动参数中指定的classPath中的jar包和目录

          User-Defined ClassLoader (用户自定义类加载器)

classLoad提供的几个关键方法

 

   loadClass()  加载指定类名的方法

               查找顺序为:  ClassLoader先从已加载的类中--------->parent classLoad

                            ------------>systemClassLoad---------->最后覆盖findClass来作

                            特殊处理

   findLoadedClass 是从当前classLoader实例对象的缓存中查询已加载的类 ,

                       调用的是本方法

  findClass  此方法直接抛出classNotFoundException

  

   findSystemClass  system classLoader 如果未找到继续从bootstrap  

                        classLoader中寻找,未找到返回空

  defineClass 将二进制字节码转换为字节对象

  

   resolveClass   完成class 对象的链接,如果链接过则直接返回

类加载过程中常抛出的异常

classNotFoundException   未找到类文件

noClassDefFoundError 加载类中引用的另外的类不存在

linkageError  在用户自定义加载器中容易出现,此类已经在classloader中加载过

                 了,重复加载会出此异常

classCastException   类型转换异常

类执行机制

字节码解释执行

     执行方法的指令:

        Invokestatic   调用静态方法

        Invokevirtual   调用对象实例的方法

        Invokeinterface  调用接口

        Invokespecial     调用private 方法和编译原码后生成的init方法

Javac 编译代码

Javap -c 查看字节码

Sun JDK 基于栈的体系结构来执行字节码

示例代码如下:

Java代码

public class Demo {

public static void foo() {

int a = 1;

int b = 2;

int c = (a + b) * 5;

}

}

//字节码如下

public cn.yue.distributed.esb_soa.Demo();

  Code:

   0:   aload_0

   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V

   4:   return

public static void foo();

  Code:

   0:   iconst_1   //将类型为int , 值为1的常量放入操作数栈

   1:   istore_0   //将操作数栈中的栈顶的值弹出放入局部变量区

   2:   iconst_2   //同上

   3:   istore_1

   4:   iload_0 //装载局部变量区中第一个值到操作数栈

   5:   iload_1  //同上装载第二个操作数

   6:   iadd      //执行int类型的add 指令 并将计算出的结果放入操作数栈

   7:   iconst_5 //将类型为int , 值为1的常量放入操作数栈

   8:   imul     // 执行int 类型的mul方法,将计算出的结果放入操作数栈

   9:   istore_2 //将操作数栈中弹出放入局部变量

   10:  return  //返回

}

指令解释执行(冯 诺一曼体系中的FDX循环方式)

获取一条指令,解码分派,然后执行

实现FDX循环时有

switch-threading

 示例代码:

 While(true){

Int code=fetchNextCode();

Switch(){

Case IADD:

       //do something

Case ...:

       //do something

 }

}

Tooken-threading

 示例代码如下;

 IADD:{

//do add;

fetchNextCode();

Dispatch();

}

Iconst_0:{

Push{0};

fetchNextCode();

Dispatch();

}

Dircet-threading

Subroutime-threading

Inline-threading

栈顶缓存  

     位于操作数栈顶的值放往寄予存器

部分栈帧共存

     一个方法调用另一个方法,传到另一个方法的参数已存放在操作数栈的数据

     Sun JDK作了一个优化,就是当调用方法时,后一方法可将前一方法的操作数栈

     作为当前方法的局部变量

编译执行

   为提升代码的执行性能 ,JDK提供将字节码编译为机器码的支持,编译在运行时执行

通常为JIT编译器

提供的两种模式为:

client compiler

                 

JDK1.6以后采用的是线性扫描寄存器分配算法

 其他方法的优化:

  方法内联

     示例代码如下

     Public class demo1(){

          ....

    demo2();

          ....

      }

     Public class demo2(){

 

      }

     //如果编译后demo2的字节数小于35个字节

 会用类以于以下的结梦

Public  class demo1(){

       //demo2

               Public class demo2(){

 

                 }

}

 

可以在JDK的启动参数上加-XX:+PrintlnLining

  去虚拟化

      在装载class文件后,如果类中的方法只提供一个实现类。对于调用了此方法的代码,也可以进行方法内联

  冗余削除

           在编译时,根据运行时情况进行代码折叠或削除

private static final Log log=LogFactory.getLog("");

private static final boolean isDebug=log.isDebugEnable();

public void execute(){

if(isDebug){

//do sth

}

//do something

}

如果isDebugEnabled返回的为false

   那么以上方法就变为:

    public void execute(){

//do something

}

 Server compiler  图着色寄存器算法(逃逸分析是进行优化的基础)

标量替换

栈上分配

同步消除

反射执行

           Class clazz = Class.forName("类名");

Method method = clazz.getMethod("方法名"null);

Object object = clazz.newInstance();

method.invoke(object, null);

 Jvm内存管理

   方法区  存放了要加载的类的信息 最小值为16m,最大值为64m

 

   堆:  存储对象实例和数组值,也可以理解为new出来的对象

         32位操作系统上最大为2G 64位机器上没有限制

         

新生代(new generation):大多数新建的对象从新生代分配内存

旧生代(old generation): 用于存放新生代中多次垃圾回收仍然存在的对象

本地方法栈  支持本地方法的执行

Pc寄存器和JVM方法栈  每个线程都会创建PC寄存器和JVM方法栈

                        Pc寄存器占用的或操作系统内存

                        JVM占用操作系统内存

                        当空间栈空间不足时,会抛stackOverFlowError

内存分配

  Java 对象占用的内存主要从堆上分配,堆是线程共享的,在分配内存时要进行加锁

  为每个新建线程分配一块独立的空间(TLAB)thread local allocation buffer 

内存回收

 收集器

引用计数收集器

跟踪收集器 

   实现方法

        复制 

        标记-清除 

        标记-压缩

 JDK中可用的GC

新生代可用GC

未看完

JVM内存状况查看和分析工具

  A 输出gc日志

      输出到控制台:

        在启动参数中:-XX+PrintGC  - XX:+PrintDetails  -XX:+PrintGCTimeStamps

                      -XX:+PrintGCApplicationStoppedTime

      输出到指定文件:

         -verbose:gc   -XX:+PtingTenuringDistribution  

  B  GC  Portal

  C  JConsole

  D  JVisualVM

  E  JMap

  F  JStat

  G  Eclipse Memory Analyzer

JVM 线程资源同步及交互机制

   线程资源同步机制  示例代码如下

        int i = 0;

public int getNextId() {

return i++;

}

以上代码的执行的步骤为:

  首先在main  memory(java堆中i分配内存 ,并存0;

   线程启动后,会分配一片working memory(操作数栈)

      i ++执行的步骤为:  装载 读取 进行i+1 操作,存储写入i

             

线程交互机制

线程状态分析

 查看线程状态

 Kill -3 [线程id]   将线程的相关信息输出到控制台

 Jstack 可以直接查看线程的运行状况

 JConsole 工具

 threadXMBean

 TDA

序列化和反序列化

性能调优

  Cpu消耗分析

 

  Linuxcpu用于中断、内核、用户进程的任务处理

   上下文切换

         每个cpu只能执行一个线程  采用抢占式调用

   运行队列  控制每个cpu核上运行队列为1-3

利用率 

     可以通过top查看进程中的CPU的消耗状态

      

       Pidstat  systat中的工具,需先安装SYSTAT

Cpu的消耗主要体现在两个方面上  us  sy

    

文件IO消耗分析

   跟踪线程的文件IO的消耗

     方法一:

       Pidstat  -d -t -p[pid] 100 类似的命令可以查看线程IO 的消耗状况    

     方法二:

       Iostat 

 

  

网络IO消耗分析

   Google修改kernel方法对网卡中断不均的问题进行修复,或是用支持MSI-X的网卡来修复

   查看网络IO消耗状况

Sar -n FULL 12

内存消耗分析

  通过vmstat    sar   top   pidstat 等方式来查看swap和物理内存消耗状况

      Vmstat  和内存相关的信息 其中swpd是大小说明物理内存不够用,将数

               据放到硬盘上的数据的大小

      Sar   -r参数可以查看内存消耗状况,可以查询历史状况 

            以上两者不能分析进程所占用的内存量

      Top  可以查看所消耗的内存量

      Pidstat  pidstat -r -p[pid] [interval][times]

对物理内存的消耗

   实现对物理内存的直接操作

public static void main(String[] args) throws InterruptedException {

Thread.sleep(20000);

System.out.println("read to create bytes, so JVM heep will be used");

byte[] bs = new byte[128 * 1000 * 1000];

bs[0] = 1;

bs[1] = 2;

Thread.sleep(10000);

System.out.println("read to allocate & put direct bytebuffer, no JVM heep should be used");

ByteBuffer byteBuffer = ByteBuffer.allocate(128 * 1024 * 1024);

byteBuffer.put(bs);

byteBuffer.flip();

Thread.sleep(10000);

System.out.println("read to gc,JVM heep will be freed");

bs = null;

System.gc();

Thread.sleep(10000);

System.out.println("read to get bs,then JVM heap will bi used");

byte[] resultbytes = new byte[128 * 1000 * 1000];

byteBuffer.get(resultbytes);

System.out.println("resultbytes[1] is: " + resultbytes[1]);

Thread.sleep(10000);

System.out.println("read to gc all");

byteBuffer = null;

resultbytes = null;

System.gc();

Thread.sleep(10000);

}

程序执行慢原回分析 

   

  锁竟争激烈

  未充分使用硬件资源

  数据量增长   jprofiler可以查看记录程序的时间消耗

    

性能调优

可以从硬件  操作系统  JVM  程序  

JVM调优

   代大小的调优

      在不采用G1 的情况下  通常minor GC 会快于full GC 

       各个代大小的设置决定了minor GCfull GC触发的时机

程序调优

   Cpu  us高的解决方法

     添加 thread.sleep(),以释放CPU的执行权

   Cpu  sy高的解决方法  

      减少线程数

      网络IO并发操作,可以采用协程(coroutine)来支撑更高的并发量

       实现协程的框架为killim

文件IO消耗严重

  异步写文件  

  批量读写

  限流

  限制文件大小

网络IO消耗

   释放不必要的引用

   使用对象缓存池

   采用合理的缓存失效算法  建立缓存池 FIFO  LRU  LFU

你可能感兴趣的:(java,jvm,jdk,ClassLoader,IO,Class)