Skip to content

JVM

《The Java Virtaul Machine Specifiacation Java SE8 Edition》
JSR-133 《Java Memory Model and Thread Specifiacation》

The class File Format

编译后被 JVM所执行的代码使用了一种平台中立不依赖于特定硬件及操作系统)的二进制格式。并且经常(但非绝对)以文件的形式存储,因此这种格式称之为class文件格式。class文件格式中精确地定义了类与接口的表示形式,包括在平台相关的目标文件格式中一些细节上的惯例,例如字节序(byte ordering:Big-Endian、Little-Endian)等。

Data Types

与 Java 程序语言中的数据类型相似,JVM可以操作的数据类型可分为两类:原始类型(primitive type)和引用类型(reference type)。与之对应,也存在原始值(primitive value)和引用值(reference value)两种数据类型,他们可用于变量赋值参数传递方法返回运算操作

实际类型运算类型分类
booleanint
byteint
charint
shortint
intint
floatfloat
referencereference
returnAddressreturnAddress
longlong
doubledouble

Primitive Types and Values

JVM所支持的原始数据类型包括数值类型(numeric type)、boolean 类型、returnAddress 类型三类。
数值类型又分为整数类型(integral type)、浮点类型(floating-point type)两种。

The returnAddress Type and Values

returnAddress 类型会被 JVM的jsrretjsr_w指令所使用。(这几个指令以前主要用来实现 finally 语言块,后来改为冗余 finally 代码块的方式来实现,甚至到了 JDK 7 时, JVM 已不允许 class 文件内出现这几个指令。那相应地,returnAddress 类型就处于名存实亡地状态)。
returnAddress 类型的值指向一条 VM 指令的操作码。
returnAddress 类型在 Java 语言中并不存在相应的类型,而且也无法在程序运行期间更改。

Reference Types and Values

JVM中有三种引用类型:类类型(class type)、数组类型(array type)和接口类型(interface type)。这些引用类型的值分别指向动态创建的类实例数组实例和实现了某个接口的类实例或数组实例

Run-Time Data Area

JVM定义了若干这种程序运行期间会使用到的运行时数据区,其中有些会随着 VM 启动而创建,随着 VM 退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。

pc Registry

JVM 可以支持多条线程同时执行,每一条 JVM线程都有自己的 pc(program counter)寄存器。在任意时刻,一条 JVM线程只会执行一个方法的代码,这个正在被线程执行的方法称为该线程的当前方法(current method)。
如果这个方法不是 native 的,那 pc 寄存器就保存 JVM正在执行的字节码指令地址。
如果该方法是 native 的,那 pc 寄存器的值 是 undefind。
pc 寄存器的容量至少应当能保存一个 returnAddress 类型的数据或者一个与平台相关的本地指针的值。

Java Virtual Machine Stacks

每一条 JVM 线程都有自己私有的 JVM栈(Java Virtual Machine Stack),这个栈与线程同时创建,用于存储栈帧(Frame)。JVM栈帧的作用与传统语言中的栈非常相似,用于存储局部变量与一些尚未计算好的结果。另外,它在方法调用和返回中也扮演了很重要的角色。因为除了栈帧的出栈和入栈之外,JVM 不会再受到其他因素的影响,所以栈帧可以在堆内存中分配,JVM栈所使用的内存不需要保证是连续的。

异常情况:

  • 如果线程请求分配的栈容量超过 JVM栈允许的最大容量,JVM将会抛出异常 StackOverFlowError。
  • 如果 JVM栈可以动态扩展,并且再尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的 VM 栈,那 JVM将会抛出异常 OutOfMemoryError。

Java Heap

在 JVM 中,(heap)是可供各个线程共享的运行时内存区域,也就是提供所有类实例和数组对象分配内存的区域。
Java 堆在 VM 启动的时候就被创建,它存储了被自动内存管理系统(automatic storage management system,也就是常说的 garbage collector(垃圾收集器))所管理的各种对象,这些受管理的对象无需也无法显示地销毁。JVM堆所使用的内存不需要保证是连续的。

异常情况:

  • 如果实际所需的堆超过了自动内存管理系统能提供的最大容量, 那么 JVM 将会抛出 异常OutOfMemoryErorr

Method Area

在 JVM 中,方法区(method area)是可供各个线程共享的运行时内存区域。方法区与传统语言中的编译代码存储区(storage area for compiled code)或者操作系统进程的正文段(text segment)的作用非常类似,它存储了每一个类的结构信息,例如,运行时常量池(runtime constant pool)、字段和方法数据、构造函数和普通方法的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。
方法区在 VM 启动的时候创建,方法区是堆逻辑组成部分。

异常情况:

  • 如果方法区的内存空间不能满足内存分配请求,那么 JVM 将抛出异常 OutOfMemoryError

Run-Time Constant Poll

运行时常量池(runtime constant pool)是 class 文件中每一个类或接口的常量池表(constant pool table)的运行时表示形式,它包括了若干种不同类型的常量,从编译期可知的数值字面量到必须在运行期解析后才能获得的方法或字段引用。运行时常量池类似于传统语言中的符号表(symbol table),不过它存储数据的范围比通常意义上的符号表要更为广泛。
每一个运行时常量池都在 JVM 的方法区中分配,在加载类和接口到 VM 后,就创建对应的运行时常量池。

异常情况:

  • 当创建类或接口时,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那么 JVM 将会抛出异常 OutOfMemoryError。

Native Method Stacks

JVM 实现可能会使用到传统的栈(通常称为 C stack)来支持 native 方法(指使用 Java 以外的其他编程语言编写的方法)的执行,这个就是本地方法栈(native method stack)。当 JVM 使用其他语言来实现指令集解释器时,也可以使用本地方法栈。如果 JVM 不支持 native 方法,或本身不依赖传统栈,那么可以不提供本地方法栈,如果支持本地方法栈,那么这个栈一般会在线程创建的时候按线程分配。

异常情况:

  • 如果线程请求分配的栈容量超过本地方法栈允许的最大容量,JVM 将抛出异常 StackOverFlowError。
  • 如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存区创建对应的本地方法栈,那么 JVM 将抛出异常 OutOfMemoryError。

Frames

栈帧(frame)是用来存储数据和部分过程结果的数据结构,同时也用来处理动态链接(dynamic linking)、方法返回值和异常分派(dispatch exception)。
栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。栈帧的存储空间有创建它的线程分配在 JVM Stack 之中,每一个栈帧都有自己的本地变量表(local variables)、操作数栈(operand stack)和指向当前方法所属的类的运行时常量池的引用
栈帧中还允许携带于 JVM 实现相关的一些附加信息,例如,对程序调试提供支持的信息。
本地变量表和操作数栈的容量在编译期确定,并通过相关方法的 code 属性保存及提供给栈帧使用。因此栈帧的数据结构的大小仅仅却决于 JVM 的实现。

在某条线程执行过程中的某个时间点上,只有目前正在执行的那个方法的栈帧是活动的。这个栈帧称为当前栈帧(current frame),这个栈帧对应的方法称为当前方法(current method),定义这个方法的类称作当前类(current class)。对局部变量表和操作数栈的各种操作,通常都指的是对当前栈帧的局部变量表和操作数栈所进行的操作。
如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不在是当前栈帧了。调用新的方法是,新的栈帧也会随之而创建,并且会随着程序控制权移交到新方法而成为新的当前栈帧。方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,然后, VM 会丢弃当前栈帧,使得前一个栈帧重新称为当前栈帧。

Local Variables

每个栈帧内部都包含一组称为局部变量表的变量列表。栈帧中局部变量表的长度由编译期决定,并且存储与类或接口的二进制表示之中,即通过方法的 code 属性保存提供给栈帧使用。
一个局部变量可以保存一个类型为 boolean、byte、char、short、int、float、reference 或 returnAddress 的数据。两个局部变量可以保存一个类型为 long 或double 的数据。
局部变量使用索引来进行定位访问。首个局部变量的索引值为0,局部变量的索引是个整数,它大于等于0,且小于局部变量表的长度。
long 和 double 类型的数据占用两个连续的局部变量,这两种类型的数据值采用两个局部变量中较小的索引值来定位。例如,将一个 double 类型的值存储在索引值为 n 的局部变量中,实际上的意思是索引值为 n 和 n+1 的两个局部变量都用来存储这个值。然而,索引值为 n+1 的局部变量是无法直接读取的,但是可能会被写入。不过,如果进行了这种操作,那将会导致局部变量 n 的内容失效。
前面提及的局部变量索引值 n 并不要求一定是偶数, JVM 不要求 double 和 long 类型数据采用 64-bit 对齐的方式连续地存储在局部变量表中。VM 实现者可以自由地选择适当的方式,通过两个局部变量来存储一个 double 或 long 类型的值。
JVM 使用局部变量表来完成调用时的参数传递。当调用类方法时,它的参数将依次传递到局部变量表中从 0 开始的连续位置上。当调用实例方法时,第 0 个局部变量一定用来存储改实例方法所在对象的引用(即 Java 语言中的 this 关键字)。后续的其他参数将会传递到局部变量表中从 1 开始的连续位置上。

Operand Stacks

每个栈帧内部都包含一个称为操作数栈的后进先出(Last-In-First-Out,LIFO)栈。栈帧中操作数栈的最大深度由编译期决定,并且通过方法的 code 属性保存提供给栈帧使用。
在上下文明确不会产生误解的前提下,我们经常把“当前栈帧的操作数栈”直接简称为“操作数栈”。
栈帧在刚刚创建时,操作数栈是空的。 JVM 提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量到操作数栈中,也提供了一些指令用来从操作数栈取走数据、操作数据以及把操作结果重新入栈。在调用方法时,操作数栈也用来准备调用方法的参数以及接收返回结果。
例如,iadd字节码指令的作用是将两个 int 类型的数值相加,它要求在执行之前操作数栈的栈顶已经存在两个由前面的其他指令所放入的 int 类型数值。在执行iadd指令时,两个 int 类型数值从操作栈中出栈,相加求和,然后将求和结果重新入栈。在操作数栈中,一项运算常由多个子运算(subcomputation)嵌套进行,一个子运算过程的结果可以被其他外围运算所使用。
操作数栈的每个位置上可以保存一个 JVM 中定义的任意数据类型的值,包括 long 和 double 类型。
在操作数栈中的数据必须正确地操作。例如,不可以入栈两个 int 类型数据,然后当作 long 类型去操作,获取入栈两个 float 类型的数据,然后使用iadd指令对他们求和。
有一小部分 JVM 指令(例如dupswap指令)可以不关注操作数的具体数据类型,把所有在运行时数据区中的数据当作裸类型(raw type)数据来操作,这些指令不可以用来修改数据,也不可以拆散那些原来不可拆分的数据,这些操作的正确性将会通过 class 文件的校验过程来强制保障。
在任意时刻,操作数栈都会有一个确定的栈深度,一个 long 或 double 类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位的栈深度。

Dynamic Linking

每个栈帧内部都包含一个指向当前方法所在类型的运行时常量池的引用,以便当前方法的代码实现动态链接。在 class文件里面,一个方法若要调用其他方法,或者访问成员变量,则需要通过符号引用(symbolic reference)来表示,动态链接的作用就是将这些以符号引用所表示的方法转换为对实际方法的直接引用。类加载的过程中将要解析尚未被解析的符号引用,并将变量访问转换为与这些变量的运行时位置相关的存储结构中的适当偏移量。

由于对其他类中的方法和变量进行了后期绑定(late binding),所以即便那些类发生了变化,也不会影响调用它们的方法。

Normal Method Invocation Completion

方法调用正常完成是指在方法的执行过程中,没有抛出任何异常——包括直接从 JVM 中抛出的异常以及在执行时通过 throw 语句显示抛出异常。如果当前方法调用正常完成,它很可能会返回一个值给调用它的方法。方法正常完成发生在一个方法执行过程中遇到了方法返回的字节码指令时,使用哪种返回值指令取决于方法返回值的数据类型(如果有返回值)。

在这种场景下,当前栈帧承担着恢复调用者状态的责任,包括调用者的局部变量表和操作数栈,以及正确递增程序计数器,以跳过刚才执行的方法调用指令等。调用者的代码在被调用方法的返回值压入调用者操作数栈后,会继续正常执行。

Abrupt Method Invocation Completion

方法调用异常完成是指在方法的执行过程中,某些指令导致了 JVM 抛出异常,并且 VM 抛出的异常在该方法中没有办法处理,或者在执行过程中遇到athrow字节码指令并显示地抛出异常,同时在该方法内部没有捕获异常。如果方法异常调用完成,那一定不会有返回值返回给其调用者。

Instruction Set Summary

JVM 的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。VM中许多指令并不包含操作数,只有一个操作码。 操作数的数量以及长度取决于操作码,如果一个操作数长度超过一个字节,那么它将会以 big-endian 顺序储存。

big-endian: (byte1 << 8) | byte2

助记表

数据类型助记符
byteb
charc
shorts
inti
longl
floatf
doubled
referencea
常量池索引#

指令表

opcodebyteshortintlongfloatdoublecharreference
Tipushconst -> stackbipushsipush
Tconstconst -> stackiconstlconstfconstdconstaconst
Tloadvar -> stackiloadlloadfloaddlocalaload
Tstorestack -> varistorelstorefstoredstoreastore
Tincincrementiinc
Taloadvar -> stackbaloadsaloadialoadlaloadfaloaddaloadcaloadaaload
Tastorestack -> varbastoresastoreiastorelastorefastoredastorecastoreaastore
Taddaddiaddladdfadddadd
Tsubsubtractisublsubfsubdsub
Tmulmultiplyimullmulfmuldmul
Tdivdivideidivldivfdivddiv
Tremremainderiremlremfremdrem
Tnegnageteineglnegfnegdneg
Tshlshift leftishllshl
Tshrshift rightishrlshr
Tushrunsign shift rightiushrlushr
Tand&iandland
Tor|iorlor
Txor^ixorlxor
i2Ttype conversioni2bi2si2li2fi2d
l2Tl2il2fl2d
f2Tf2if2lf2d
d2Td2id2ld2f
Tcmp比较lcmp
Tcmpl比较fcmpldcpml
if_TcmpOP比较if_icmpOPif_acmpOP
Treturnireturnlreturnfreturndreturnareturn
sh
a ^ b = (a & !b) | (!a & b);
a ^ b = (a | b) & (!a & b);

算术指令

算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈。大体上可分两种:整数运算指令和浮点数运算指令。

运算种类

加法、减法、乘法、除法、求余、求负、位移、位与、位或、位异或、局部变量自增、比较

JVM 没有规定整型数据溢出,整数除法指令、整数 求余指令在除数位零时会导致 JVM 抛出异常。(ArithmeticException)

宽化类型转化

widening numberic conversion,小范围类型向大范围类型的安全转换

  • int -> long、float、double
  • long -> float、double
  • floar -> double

窄化类型转化

narrowing numeric conersion,窄化类型转换可能会导致转换结构具备不同的正负号、不同的数量级,因此,转换过程很可能会的导致数值丢失精度。

  • int -> byte、short、char
  • long -> int
  • float -> int、long
  • double -> int、long、float

对象的创建和操作

指令
new创建类实例
newarray、anewarray、multianewarray创建数组的
getfield、putfield、getstatic、putstatic访问类字段(static 字段)和类实例字段(非 static 字段)
baload、caload、saload、iaload、laload、faload、daload、aaload数组元素加载到操作数栈
bastore、castore、sastore、iastore、lastore、fastore、dastore、aastore操作数栈的值存储到数组元素
arraylength读取数组长度
instanceof、checkcast检查类实例或数组实例类型
pop、pop2、dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2、swap直接控制操作数栈
ifeq、ifne、iflt、ifle、ifgt、ifge、ifnull、ifnonnull条件分支
if_icmpeq、if_icompne、if_icmplt、if_icmple、if_icmpgt、if_icmpge条件分支
if_acmpeq、if_acompne条件分支
tableswith、lookupswith复合条件分支
goto、goto_w、jsr、jsr_w、ret无条件分支
invokevirtual调用对象的实例方法,根据对象的实际类型进行分派。
invokeinterface调用接口方法,它会在运行时搜索由特定对象所实现的这个接口方法,并找出适合的方法进行调用。
invokespecial调用一些特殊处理的实例方法,包括实例初始化方法、私有方法|
invokestatic调用类方法(static 方法)
invokedynamic调用以绑定了 invokedynamic 指令的调用点对象(call site object)作为目标的方法。调用点对象时一个特殊的语法结构,当一条 invokedynamic 指令首次被 JVM 执行前,JVM 会执行一个引导方法(bootstrap method)并以这个方法的运行结果作为调用点队形。因此,每条 invokedynamic 指令都是独一无二的链接状态,这是它与 其他方法调用指令的一个差异。
athrow显示抛出异常,JVM 指令检测到异常状态时由 JVM 自动抛出。

boolean、byte、char、short 类型的条件分支比较操作,都使用 int 类型的比较指令来完成。

对于 long、float、double 类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整型数值到操作数栈中,随后再执行 int 类型的条件分支比较操作来玩车个整个分支跳转。

浮点数运算遵循 IEEE 754 标准。所有运算结果都必须舍入到适当的精度,非精确的结果必须舍入为可表示的最接近的精确值,如果有两种可表示的形式与该值接近,那将优先选择最低有效位为零的。(默认舍入模式,也成为想最接近数舍入模式)

浮点数转化为整数类型时,使用 IEEE 754 标准中的向零舍入模式,这种模式会导致数字被截断,表示小数的部分将会被丢弃。

同步

JVM 支持方法级同步和方法内部一段指令序列的同步,这两种同步结构都是使用同步锁(monitor)来支持的。 方法级的同步是隐式的 ,即无需通过字节码指令来控制,它实现在方法调用和返回操作之中,JVM 可以从常量池的方法表结构(method_info_structure)中的 ACC_SYNCHRONIZED 访问标志是否设置,如果设置了,执行线程将先持有同步锁,然后执行方法,

virtual machie assembly language<index> <opcode> [<operand1> [<operand2>...]] [comment]

index: 当前指令的操作码集合中的地址偏移量

opcode
storeoperand stack -> local variables从操作数栈弹出一个值保存到局部变量
loadlocal variables -> operand stack将局部变量的值压入操作数栈
pushlocal variables -> operand stack
constconstant -> operand stack
inclocal variable increment直接对局部变量进行自增操作
cmpltcompare less than
goto

JVM 基于栈架构设计的,JVM 的大多数操作是从当前栈帧的操作数栈取出一个或多个操作数,或将结果压入操作数栈中。每调用一个方法,都会创建一个新的栈帧,并创建对应方法所需的操作数栈和局部变量表。每个线程在运行的任意时刻,都会包含若干个由嵌套的方法调用而产生的栈帧,同时也会包含等量操作数栈,但是只有当前栈帧中的操作数栈才是活动的。

int

java
void spin() {
	int i;
	for (i = 0; i < 100; i++) {
		; // Loop body is empty
	}
}

编译后代码如下:

asm
Method void spin()
0	iconst_0	// Push int constant 0
1	istore_1	// Store into local vairable 1 (i=0)
2	goto 8		// First time through don't increment
5 	iinc 1 1	// Increment local variable 1 by 1 (i++)
8	iload_1		// Push local variable 1 (i)
9 	bipush 100	// Push int constant 100
11	if_icmplt 5	// Compare and loop if less than (i < 100)
14	return		// Return void when done

循环部分构成

asm
5 	iinc 1 1	// 第一个操作数:指局部变量表的第一个变量;第二个操作数:指值增加一。
8	iload_1		
9 	bipush 100	
11	if_icmplt 5	// 操作数栈弹出两个值进行比较,满足条件则跳到5。

double

java
void dspin() {
	double i;
	for (i = 0.0; i < 100; i++) {
		;
	}
}

编译后代码如下:

asm
Method void dspin()
0	dconst_0	// Push double constant 0.0
1	dstore_1	// Store into local variables 1 and 2
2	goto 9		// First time through don't increment
5	dload_1		// Push local varibles 1 and 2
6	dconst_1	// Push double constant 1.0
7	dadd		// Add; there is no dint instruction
8	dstore_1	// Store result in local variables 1 and 2
9	dload_1		// Push local variables 1 and 2
10 	ldc2_w #4	// Push double constant 100.0
13	dcmpg		// There is no if_dcmplt instruction
14	iflt 5		// Compare and loop if less than (i < 100.0)
17	return		// Return void when done

double、long 类型数值占用两个局部变量的空间,但是只能通过两个局部变量中银锁较小的一个进行访问。 注意,局部变量表周昂使用了一对局部变量来存储 doubleLocals 方法中的 double 值,这对局部变量不能分开操作。

short

java
void sspin() {
	short i;
	for (i = 0; i < 100; i++) {
		;
	}
}

编译后代码如下:

asm
Method void sspin()
0	iconst_0
1	istore_1
2	goto 10
5	iload_1		// The short is treated as though an int
6	iconst_1
7	iadd
8	i2s			// Truncate int to short
9	istore_1
10	iload_1
11	bipush 100
13	if_icmplt 5
16	return

Java virtual machine 中,因缺乏对 byte、char、short 类型数据的直接操作指令,所以操作时这些类型的值会自动提升为 int 类型并使用 int 的指令。 额外的代价,要将操作结果截短到他们的有效范围内。

对于浮点类型(float、double)它们仅缺少了条件转移指令部分,其他操作的支持程度与 int 相同。

算术运算

Java virtual machine 通常是基于操作数栈进行算数运算,只有 iinc 指令例外,它直接对局部变量进行自增操作。 算术运算使用到的操作数是从操作数栈中弹出的,运算结果被压回操作数栈中。在内部运算时,中间运算(arithmetic subcomputation)的结果可以被当作操作数使用。

计算机科学中常见操作,特别在处理内存分配和数据对齐时。 将一个整数对齐到某个2的幂

aligned_x=(x+221)&(2n1)
  1. x+2n1:确保x2n的倍数之间的位置被移动到最接近的2n的倍数的前一个位置。
  2. (2n1):生成掩码。这个掩码在2n 的倍数位置上是1,其他位置是0。
  3. &:将x+2n1与掩码相与,得到最接近的、不小于x2n倍数。
java
int align2grain(int i, int grain) {
	return ((i + grain-1) & ~(grain-1));
}

编译后代码如下:

asm
Method int align2grain(int,int)
0	iload_1
1	iload_2
2	iadd
3	iconst_1
4	isub
5	iload_2
6	iconst_1
7	isub
8	iconst_m1
9	ixor
10	iand
11	ireturn

访问运行时常量池

opcode
ldcbyte、char、short
ldc_w当运行时常量池条目过多时(多余256,即1字节能表示的范围),使用 ldc_w 取代 ldc 指令
ldc2_wdouble、long
java
void useManyNumberic() {
	int i = 100;
	int j = 1000000;
	long l1 = 1;
	long l2 = 0xffffffff;
	double d = 2.2;
}

编译后代码如下:

asm
Method void userManyNumeric()
0	bipush 100	// Push small int constant with bipush
2	istore_1
3	ldc #1		// Push large int constant (1000000) with ldc
5	istore_2
6	lconst_1	// A tiny long value uses small fast lconst_1
7	lstore_3
8	ldc2_w #6	// Push long 0xffffffff (this is , an int -1)
				// Any long constant value can be pushed with ldc2_w
11	lstore 5
13	ldc2_w #8	// Push double constant 2.200000
				// Uncommon double values area also pushed with ldc2_w
16	dstore 7

控制结构

if-then-else do while break continue switch ry-catch-finally while-int

java
void whileInt() {
	int i = 0;
	while (i < 100) {
		i++;
	}
}

编译后:

asm
Method void whileInt()
0	iconst_0
1	istore_1
2	goto 8
5	iinc 1 1
8	iload_1
9	bipush 100
11	if_icmplt 5
14	return

while-double

java
void whileDouble() {
	double i = 0.0;
	while (i < 100.1) {
		i ++;
	}
}

编译后:

asm
Method void whileDouble()
0	dconst_0
1	dstore_1
2	goto 9
5	dload_1
6	dconst_1
7	dadd
8	dstore_1
9	dload_1
10	ldc2_w #4	// Push double constant 100.1
13	dcmpg
14	iflt 5
17	return

dcmpg ifge

java
int lessThen100(double d) {
	if (d < 100.0) {
		return 1;
	} else {
		return -1;
	}
}

编译后:

asm
Method int lessThen100(double)
0	dload_1
1	ldc2_w #4	// Push double constant 100.0
4	dcmpg 	// Push 1 if d is NaN or d > 100.0
			// Push 0 if d == 100.0
			// Push -1 if d < 100.0
5	ifge 10	// Branch on 0 or 1
8	iconst_1
9	ireturn
10	iconst_m1
11	ireturn

dcmpl iflt

java
int greatThen100(double d) {
	if (d > 100) {
		return 1;
	} else {
		return -1;
	}
}

编译后:

asm
0	dload_1
1	ldc2_w #4	
4	dcmpl 	// Push -1 if d is NaN or d < 100.0
			// Push 0 if d d == 100.0
5	ifle 10	// Branch on 0 or -1
8	iconst_1
9	ireturn
10	iconst_m1
11	ireturn

接受参数

JAR

shell
# 解压
jar -xvf <*.jar
# 压缩打包 
# 当前目录所有文件
jar -cvfM0 <target.jar*
# 指定打包目录
jar -cvfM0 <target.jar[dir/]..

Web

Session-Cookie

认证后,服务器保存 session 信息。SessionId 会通过 Cookie 传递给前端,请求服务器时会带有 Cookie。这种方式称为有状态

JWT, Json Web Token

JWT 作用是授权(Authenorization),而不是认证(Authentication)

用户认证,指的是验证用户的身份,即用户登录。

用户授权,拥有特定权限访问允许特定资源。

run script

  • JVM 规范
    • 程序计数器:当前线程执行的字节码的行号指示器,线程私有。
    • 虚拟机栈:Java 方法执行的内存模型,每个 Java 方法的执行对应着一个栈帧的进栈和出栈操作。
    • 堆:对象内存分配的地方,内存垃圾回收的主要区域,所有线程共享。分为,新生代、老年代。
    • 方法区:用于存储已经被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。HotSpot 中的 “永久代”。
    • 常量池:方法区的一部分,存储常量信息。如,字面量、符号引用等。
    • 直接内存
bash
s0='/bin/nohup /bin/java -jar <JVM_PARAMS<PRGM_PGE<PRGM_PARAMS<LOG_DIR2&1 &';
s1=$(cd $(dirname $0); pwd -P);
# jdk8+ 持久代替换为元数据区
# heap=Xmx + 
arr0=(-Xms1024m -Xmx1024m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4);
for e in ${arr0[*]}; do s2+=" ${e}"; done

s3="${s1}/target.jar";

arr1=(--spring.config.localtion="${s1}/config/" --spring.profiles.active=test --spring.main.allow-bean-definition-overriding=true);
for e in ${arr1[*]}; do s4+=" ${e}"; done

s5="$s1/log/$(date +%Y-%m-%d).log";

s0=$(echo ${s0} | sed -e "s#<JVM_PARAMS#${s2}#g" -e "s#<PRGM_PGE#${s3}#g" -e "s#<PRGM_PARAMS#${s4}#g" -e "s#<LOG_DIR#${s5}#g");

s6=$(netstat -tunlp | grep :::8889 | awk '{ print $7 }' | awk -F '/' '{ print $1 }');

if [[ ${s6} ]]; then kill -9 ${s6} && echo "kill process ${s6}"; fi

eval ${s0} && echo "run ${s0}";

Java虚拟机(二):JVM内存模型和OOM_jvm内存和oom关系-CSDN博客

java tomcat xms_配置tomcat服务器内存大小中的Xms、Xmx、PermSize、MaxPermSize 详解-CSDN博客

介绍 - JVM Specification (Java SE 17 Edition) Chinese Translation

推荐阅读 《Design pattern: Elements of Reusable Object-Oriented Software》Addison Wesley 《Core J2EE Patterns: Best Parctices and Design Strategies》 Prentice Hall

规范引用 J2EE 1.3 EJB 2.0 Servlet 2.3 JSP 1.2

软硬件需求 Java 2Platfrom, Standard Edition SDK v1.3 Oracle 8.1.7i Apache Log4j 1.2 Jsp Standerd Tag Library (JSTL) 1.0 JUnit 3.7 Apache Cactus J2EE Apache Velocity 1.3 Lutris XMLC 2.1 IText PDF Domify XML Apache ant

第一章

  • 分布式和非分布式应用,以及怎样选择合适的模型
  • 何时使用 EJB
  • J2EE 应用的数据存取策略
  • 4 个 J2EE 体系结构,以及怎样再它们之间进行选择
  • Web 层涉及
  • 可移植性问题

体系结构目标

  • 坚固可靠 高质量代码
  • 可工作和可伸缩
  • 利用 OO 设计