线程私有:虚拟机栈、本地方法栈、程序计数器 线程共享:堆、方法区
即如何判断一个对象所占用的内存是否该回收? 有两种方法:1、引用计数法;2、可达性分析法。 - #####引用计数法 该方法容易出现循环引用的问题,JVM并未采用。 - #####可达性分析法 判断是否能从GCRoots中找到一条到达该对象的路径。 GCRoots包括:栈中变量引用的对象、方法区中静态属性(static)引用的对象、方法区中常量(final)引用的对象。
平台无关性:通过Java虚拟机,Java代码可以运行在不同的操作系统上。 语言无关性:不同的语言通过编译成字节码,均可以运行在JVM上。
(1)、 两种数据类型:无符号数,表。 (2)、魔数:“CAFEBABE” (3)、次版本号,主版本号 (4)、常量池:常量个数,常量项(类型tag+内容)。包括字面量和符号引用。 (5)、访问标志:是否 public,final,super,interface,abstract,synthetic,annotation,enum (6)、类索引,父类索引,接口索引 (7)、字段表 (8)、方法表 (9)、属性表
(1)、字节码中的数据类型:byte、short、int、long、float、double、char、reference (2)、加载和存储指令:将数据加载到操作数栈,将数据存储到局部变量表 (3)、运算指令:加减乘除、取余、取反、位移、位运算(与、或、异或)、局部变量自增、比较 (4)、类型转换 (5)、对象创建和访问 (6)、操作数栈相关指令 (7)、控制转移指令:条件分支等 (8)、方法调用和返回: invokevirtual(实例方法) invokeinterface(接口方法) invokespecial(构造方法,私有方法,父类方法) invokestatic(静态方法) (9)、同步指令:monitorenter、monitorexit。通过管程实现,用于支持synchronized关键字。
(1)、new(使用new实例化对象的时候),getstatic(读取一个类的静态字段,不包括final的),putstatic(设置一个类的静态字段,不包括final的),invokestatic(调用一个类的静态方法时)。 (2)、通过java.lang.reflect包的方法,对类进行反射调用的时候,若类未初始化,则会对其进行初始化。 (3)、当初始化一个类时,若其父类还未初始化,则先初始化其父类。 (4)、虚拟机启动时,会先初始化包含main方法的那个类,即主类。 (5)、被动引用不会导致类加载,比如:通过子类引用父类的静态字段,不会导致子类初始化;定义某类的数组,则该类不会初始化;引用某类的静态常量,则该类不会初始化。
(1)、加载 通过类的全限定类名获取二进制流;将字节流转换为方法区内的运行时数据结构;在内存中生成一个代表该类的Class对象,作为方法区里这个类的各种访问数据的入口。 (2)、验证 校验Class文件中的信息是否复合JVM的要求。 文件格式验证(基于二进制字节流,校验主次版本号是否支持等); 元数据验证(是否继承的final类,是否实现了接口的所有方法等); 字节码验证(通过数据流和控制流分析,验证程序语义,比如只能父类引用指向子类对象,子类引用不能指向父类对象); 符号引用验证(当JVM将符号引用转换为直接引用时,会检查是否能根据名称找到相应的类,方法,字段等); 验证阶段其实不是必须的,如果该字节码被反复验证过,其实可以关闭验证。 (3)、准备 为静态变量设置初始值(int为0,reference为null等); 为常量设置初始值。 (4)、解析 将常量池里的符号引用解析为直接引用(即指向内存中某个区域的指针)。 解析的符号引用有:类或接口、字段、方法、接口方法等。 (5)、初始化 若父类没有加载,则先加载父类; 然后为静态变量设置初始值,执行静态代码块等;
(1)、每个类,都要由“加载它的类加载器”和“这个类本身”一块确定该类在虚拟机里的唯一性。 (2)、类加载器种类 启动类加载器:加载/lib目录中的类; 扩展类加载器:加载/lib/ext目录中的类; 应用程序类加载器:加载CLASSPATH中的类,如果应用程序没有自定义过自己的类加载器,这个便是程序中默认的类加载器。 (3)、双亲委派模型
当一个类加载器加载类的时候,首先不会亲自加载这个类,而是会把这个请求委派给父加载器去加载,如此递归下去,所有的请求最终会传到顶层的引启动类加载器。只有当父加载器无法加载该类时,才会让子类去尝试加载。 这样做可以保证,同一个类在虚拟机中不会被不同的类加载器加载很多次。
程序执行时,内存中的栈,里面是一个一个的栈帧。每一个方法的调用及其执行,都对应着一个栈帧。
(1)、方法调用指令 invokestatic:调用静态方法 invokespecial:调用实例构造器、私有方法、父类方法 invokesvirtual:调用所有的虚方法(非static非final方法) invokeinterface:调用接口方法 (2)、分派 静态分派:对应于方法参数上的重载。编译器在重载时,是通过参数的静态类型,而不是实际类型作为判断依据的。 动态分派:对应于多态。当invokespecial指令执行时,第一步就是在运行期确定接受者的实际类型。
(1)、编译过程 词法分析,语法分析–> 填充符号表–> 处理注解–> 语义分析(标注检查、数据及控制流分析、解语法糖)–> 字节码生成。 (2)、Java的语法糖 泛型与类型擦除 自动装箱、拆箱 foreach循环 变长参数 注意:1、JVM在字节码里,用Signature属性存储了方法在字节码层面的方法签名,通过这项元数据,可以通过反射获取类的泛型信息。2、包装类的“==”运算,在不遇到算数运算时不会自动拆箱,这时候比较的是引用是否相等。
(1)JIT编译器(Just In Time) Java程序最初是通过解释执行的,当虚拟机发现“某个方法或某段代码块 ”运行特别频繁时,会把这些代码认定为“热点代码(Hot Spot Code)”。为了提高热点代码的执行效率,在运行时,会把这些代码编译为与本地平台相关的机器码,并进行各种优化。完成这个任务的编译器叫做即时编译器。 (2)、解释器与编译器并存 1、编译对象 被多次调用的方法:将整个方法作为编译对象。 被多次执行的循环体:依然将整个方法作为编译对象,进行栈上替换(On Stack Replacement),即替换栈帧。 2、热点探测的方式 基于采样:周期性的检查栈顶,若某方法经常出现在栈顶,则其是热点方法。 基于计数器:统计方法执行次数,到了一定的阈值,则其是热点方法。 3、两种计数器 JVM的热点探测是基于计数器的,有两种计数器:方法调用计数器和回边计数器。 方法调用计数器:即统计方法执行次数。 回边计数器:循环体中代码执行的次数。“回边”,即在字节码中遇到控制流向后跳转的指令。 4、编译优化技术 公共子表达式消除、数组边界检查消除、方法内联、逃逸分析等。
(1)、JVM内存模型
1、JVM内存模型主要是定义程序中变量的访问规则,即在虚拟机中将变量存储到内存,和从内存中取出变量这样的细节。这些变量指的是实例字段、静态字段等,不包括局部变量(因为局部变量是线程私有的,不被共享,不存在竞争问题)。 2、工作内存中保存了该线程使用到的变量的在主存中的拷贝。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行,不能直接读写主存中的变量。 3、Java内存模型中的工作内存只是个抽象概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器以及其它的硬件和编译器优化。 4、内存间的交互操作 lock、unlock read、load、use、assign、store、write 5、volatile变量 6、原子性、可见性、有序性 7、happens-before原则 (2)、线程 1、线程的实现方式 三种方式:1:1、1:N、N:M。 Java是通过将线程映射到操作系统的线程上去实现的。 2、线程的调度方式 两种方式:协同式线程调度、抢占式线程调度。 Java中使用的是抢占式线程调度。 3、线程的状态转换