分布式java应用学习笔记三

深入理解JVM

 

 

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

解析执行

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

 

Java代码执行机制

编译为class文件

Java源码编译机制

 

A分析和输入的符号表(parseandenter)

Pase词法和说法分析

Enter符号输入到符号表

B处理注解(annotationprocessing)

C语义分析和生成class文件(analyseandgenerate)

 

类加载机制:

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

链接(link)

初始化(initialize):

调用了new

反射调用类中的方法

子类调用了初始化

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

 

 

类加载器有四种:

BootstrapclassLoader

ExtensionClassLoader加载扩展功能的一些jar

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

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

 

classLoad提供的几个关键方法

 

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

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

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

特殊处理

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

调用的是本方法

findClass此方法直接抛出classNotFoundException

 

findSystemClasssystemclassLoader如果未找到继续从bootstrap

classLoader中寻找,未找到返回空

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

 

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

 

 

 

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

classNotFoundException未找到类文件

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

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

了,重复加载会出此异常

classCastException类型转换异常

 

 

 

 

类执行机制

字节码解释执行

执行方法的指令:

Invokestatic调用静态方法

Invokevirtual调用对象实例的方法

Invokeinterface调用接口

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

 

Javac编译代码

Javap-c查看字节码

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

示例代码如下:

 

Java代码

publicclassDemo{

publicstaticvoidfoo(){

inta=1;

intb=2;

intc=(a+b)*5;

}

 

}

 

 

//字节码如下

publiccn.yue.distributed.esb_soa.Demo();

Code:

0:aload_0

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

4:return

 

publicstaticvoidfoo();

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){

Intcode=fetchNextCode();

Switch(){

CaseIADD:

//dosomething

Case...:

//dosomething

}

 

}

Tooken-threading

示例代码如下;

IADD:{

//doadd;

fetchNextCode();

Dispatch();

}

Iconst_0:{

Push{0};

fetchNextCode();

Dispatch();

}

 

Dircet-threading

Subroutime-threading

Inline-threading

 

栈顶缓存

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

 

部分栈帧共存

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

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

作为当前方法的局部变量

 

编译执行

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

通常为JIT编译器

提供的两种模式为:

clientcompiler

 

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

其他方法的优化:

方法内联

示例代码如下

Publicclassdemo1(){

....

demo2();

....

}

Publicclassdemo2(){

 

}

 

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

会用类以于以下的结梦

Publicclassdemo1(){

//demo2

Publicclassdemo2(){

 

}

 

}

 

 

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

 

去虚拟化

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

 

冗余削除

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

privatestaticfinalLoglog=LogFactory.getLog("");

privatestaticfinalbooleanisDebug=log.isDebugEnable();

publicvoidexecute(){

if(isDebug){

//dosth

}

//dosomething

}

如果isDebugEnabled返回的为false

那么以上方法就变为:

publicvoidexecute(){

//dosomething

}

 

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

标量替换

栈上分配

同步消除

 

 

 

 

 

反射执行

 

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

Methodmethod=clazz.getMethod("方法名",null);

Objectobject=clazz.newInstance();

method.invoke(object,null);

 

 

 

Jvm内存管理

 

 

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

 

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

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

 

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

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

 

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

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

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

JVM占用操作系统内存

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

 

内存分配

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

为每个新建线程分配一块独立的空间(TLAB)threadlocalallocationbuffer

 

 

内存回收

收集器

引用计数收集器

跟踪收集器

实现方法

复制

 

标记-清除

 

标记-压缩

 

JDK中可用的GC

 

新生代可用GC

 

未看完

 

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

A输出gc日志

输出到控制台:

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

-XX:+PrintGCApplicationStoppedTime

输出到指定文件:

-verbose:gc-XX:+PtingTenuringDistribution

BGCPortal

CJConsole

DJVisualVM

EJMap

FJStat

GEclipseMemoryAnalyzer

 

 

 

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

 

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

inti=0;

 

publicintgetNextId(){

returni++;

}

 

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

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

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

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

 

 

线程交互机制

线程状态分析

查看线程状态

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

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

JConsole工具

threadXMBean

TDA

 

 

 

 

序列化和反序列化

 

性能调优

Cpu消耗分析

 

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

上下文切换

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

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

利用率

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

 

 

Pidstatsystat中的工具,需先安装SYSTAT

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

 

 

文件IO消耗分析

跟踪线程的文件IO的消耗

方法一:

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

方法二:

Iostat

 

 

 

网络IO消耗分析

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

查看网络IO消耗状况

Sar-nFULL12

 

内存消耗分析

通过vmstatsartoppidstat等方式来查看swap和物理内存消耗状况

 

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

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

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

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

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

Pidstatpidstat-r-p[pid][interval][times]

 

 

 

对物理内存的消耗

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

publicstaticvoidmain(String[]args)throwsInterruptedException{

Thread.sleep(20000);

System.out.println("readtocreatebytes,soJVMheepwillbeused");

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

bs[0]=1;

bs[1]=2;

 

Thread.sleep(10000);

System.out.println("readtoallocate&putdirectbytebuffer,noJVMheepshouldbeused");

 

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

byteBuffer.put(bs);

byteBuffer.flip();

Thread.sleep(10000);

System.out.println("readtogc,JVMheepwillbefreed");

 

bs=null;

System.gc();

Thread.sleep(10000);

System.out.println("readtogetbs,thenJVMheapwillbiused");

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

byteBuffer.get(resultbytes);

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

 

Thread.sleep(10000);

System.out.println("readtogcall");

byteBuffer=null;

resultbytes=null;

System.gc();

 

Thread.sleep(10000);

 

}

 

 

 

程序执行慢原回分析

 

锁竟争激烈

未充分使用硬件资源

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

 

 

 

性能调优

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

 

JVM调优

代大小的调优

在不采用G1的情况下通常minorGC会快于fullGC

各个代大小的设置决定了minorGCfullGC触发的时机

程序调优

Cpuus高的解决方法

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

Cpusy高的解决方法

减少线程数

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

实现协程的框架为killim

文件IO消耗严重

异步写文件

批量读写

限流

限制文件大小

网络IO消耗

释放不必要的引用

使用对象缓存池

采用合理的缓存失效算法建立缓存池FIFOLRULFU

你可能感兴趣的:(java)