- 1. `JVM`
- ==- 类加载&执行引擎 (未)==
- - 内存结构
- - 内存溢出`OOM`
- - 垃圾回收
- ==- 性能优化(未)==
- 2. 编译 & 反编译
- 3. 语法糖
- 4. Java 反射
- 5. Java注解
- ==6. Java 泛型 (未)==
- ==7. Java 异常类型 (未)==
- 8. Java集合
- - Map
- - `HashMap`
- 9. Java关键字
- - transient
- 10. Java多线程
- 线程与进程
- ==多线程实现方式 (未)==
- 11. 控制反转和依赖注入
JVM是虚拟机。所谓的虚拟机,他是一款虚拟的计算机软件,用于执行虚拟机指令。它分为系统虚拟机和程序虚拟机。系统虚拟机提供了一个完整的可执行的操作系统,是对物理计算机的仿真。而程序虚拟机专门为执行单个程序设计,典型的代表就是JVM。在JVM执行的指令称为Java字节码指令。
JVM是一台执行Java字节码的虚拟计算机。它处于操作系统之上。它不关心在其内部运行的程序是何种语言写的,只关心语言编译成的JVM字节码文件。并且拥有独立的运行机制,只要程序语言编译形成的.class文件具备java虚拟机的内部指令集(字节码)、符号表以及其他辅助信息,JVM就可以识别、装载并运行。(这之中Java是通过javac进行编译的)因此java虚拟机可以称为多语言虚拟机。
具体地,java虚拟机是一个二进制的字节码运行环境,其装载字节码文件,接着解释或编译为对应平台的机器指令,并执行。
JVM的特点有:一次编译到处执行的跨平台性;自动垃圾回收功能;自动内存管理;可靠的即时编译器
jvm的生命周期分为开始、运行和结束三个部分。通过引导类加载器bootstrap class loader创建一个初始类来完成虚拟机进程的开始;java虚拟机的运行过程随着程序的开始而开始程序的退出而结束;其中,结束存在多种原因:程序在执行过程中遇到异常或错误而异常终止、操作系统出现错误而导致虚拟机进程终止、线程调用runtime类的exit(), halt()方法等等。
JVM----HotSpot是sun / oracle JDK, openJDK的默认虚拟机(从java 1.3开始)。我们熟悉的内存结构、GC机制都是以此虚拟机为例的。HotSpot指的就是他的热点代码探测技术。这个技术指的是:通过计数器找到最具编译价值的代码来触发即时编译或栈上替换;第二点是编译器和解释器协同工作,在最优化程序响应时间和最佳执行性能之间平衡。
- 类加载&执行引擎 (未) - 内存结构JVM内存区域分为方法区、堆、程序计数器、本地方法栈、虚拟机栈、直接内存这几个部分。
方法区用来存放被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等
堆空间分为 1/3新生代区和 2/3的老生代区。新生代区又分为Eden、Survivor(From)、Survivor(To)三个分区,分别占据的比例是8/10,1/10,1/10
程序计数器用来指示当前线程执行字节码的行号指示器
本地方法栈中JVM执行本地方法。在HotSpot中将本地方法栈和虚拟机栈合二为一
虚拟机栈 中执行的是Java方法,每个方法执行都会创建一个栈帧,其包含局部变量表、操作数栈、动态链接、方法出口四个部分。每个方法从开始调用到结束对应着一个栈帧在虚拟机栈中入栈到出栈的过程
直接内存 是通过native() 函数直接分配的堆外内存,受本机总内存和处理器寻址空间的限制
- 内存溢出OOM内存溢出可能发生在:堆、虚拟机栈/本地方法栈、方法区、直接内存以及元空间中。
堆中出现的异常是:OutOfMemoryError: java heap space,最大堆-Xms512M,最小堆-Xmx512M,新生代-Xmn128M
虚拟机中出现的异常有:虚拟机栈StackOverflowError,每个线程可使用的内存有-Xss256k,本地方法栈OutOfMemoryError: unable to create new native stack
方法区中出现的异常有:OutOfMemory: PermGen space,
最小~最大范围是:-XX:PermSize=64M ~ -XX:MaxPermSize=128M
- 垃圾回收垃圾回收首先判断是否是垃圾、然后提出了相关的垃圾回收算法,以及相关的实现即垃圾回收器,最后应该知道垃圾回收过程
判断对象已死 有引用计数算法、可达性分析算法、引用、finalize() 四种方法
- 引用计数算法 是给对象添加一个引用计数器,每有一个地方引用它时候计数器+1,引用失效时候计数器-1,计数器为0时候说明该引用失效。
-优点: 实现简单,判定效率高 -缺点是无法解决对象互相引用的问题
- 可达性分析算法 通过判断对象的引用是否可达(GC Roots — 对象)决定对象是否可以被回收。当一个对象没有引用链到GC Roots 时证明此对象不可用
GC Roots 通常是以下四种:虚拟栈(栈帧的本地变量表)中引用的对象;本地方法栈中native() 方法引用的对象;方法区中的静态变量(类静态属性)、常量引用的对象;
- 引用 分为强引用、软引用、弱引用、虚引用四种
- finalize()
垃圾回收算法 分为:标记-清除算法、复制算法、标记-整理算法、分代收集算法
- 标记-清除算法 分为标记和清除两个阶段。在标记阶段从根节点出发对存活的节点进行标记,标记完成后,然后再次扫描整个空间对未被标记的对象进行回收。
缺点:标记和清除两个过程时间效率都不高;在空间效率上,清除过程只是对对象进行回收但是并不整理因此会出现大量不连续内存碎片,在之后程序需要大量连续内存空间无法找到时候被迫提前进行再一次的GC
- 复制算法 将内存空间分为两块大小相等的部分,每次只使用其中一块,空间使用完后,将存活的对象复制到另一半内存中,把已使用的空间一次性清理掉。即每次都对半个内存进行处理
复制算法适用于存活率低的场景,比如新生代(对象进行回收时每次只有10%左右对象存活)。内存分配时候不用考虑内存碎片等情况可以进行顺序分配,实现简单运行高效。
- 标记-整理算法 在标记存活对象结束之后,在对未标记对象被清除之前,先将存活对象移到一端,然后清理掉端以外的内存。
适用于对象存活率高的场景,不会带来内存碎片问题
- 分代收集算法 根据对象的生命存活周期,也就是堆的分区,不同的区域采取不同的回收策略。提高JVM的执行效率。
新生代 新创建的对象一般放在新生代区其对象生命周期短,存活率低。回收目标是快速收集掉生命周期短的对象。因此采用复制算法。每次将对象存放到Eden区,当进行Minor GC回收,将存活对象放到 survivor0 区,清空Eden 区。当Eden 和 survivor0 都满了之后,将存活对象转移到 survivor1 区,清空survivor0, Eden 区。当survivor1区也满后,将存活对象放到老年代区。
老年代 存放经过多次(默认是15次)回收仍然在新生代存活的存活率高的对象,采用标记-清除算法或者标记-整理算法。当老年代满了之后,会进行Major GC就是 Full GC 对新生代,老年代都进行清理
永久代 存放静态文件,比如java类、方法。
Minor GC 对新生代进行垃圾回收,新生代的对象死亡频繁,采用速度快、效率高的方法
Major GC 对整个堆进行回收。速度慢,应该尽量避免。
触发Major GC 的原因主要有:老年代被写满、永久代被写满、System.gc() 被显式调用
垃圾器 对于新生代有:Serial、Parallel Scavenge、ParNew 三种 ,对于老年代有:Serial Old、Parallel Old、CMS (Cocurrent Mark Sweep) ,还有用于整个堆回收的 G1回收器
- 性能优化(未) 2. 编译 & 反编译新生代收集器 都是采用 复制算法。 Serial 是单线程收集器,Parallel Scavenge 和 ParNew 是并行收集器
老年代收集器 Serial Old 和 Parallel Old 采用标记-整理算法,CMS 采用标记-清除算法(CMS 具有高并发、低停顿特点,追求最短的GC回收停顿时间)。 同样的,Parallel 和 CMS 是并行收集器,Serial 是单线程回收器
编译分为前端编译和后端编译两部分。前端编译指的是通过:词法分析、语法分析、语义分析,生成中间代码即字节码文件的过程。后端编译是通过:机器无关代码优化、代码生成、机器相关代码优化生成机器代码的过程。
java的前端编译器主要有比如Javac、Eclipse JDT(java development toolings)的增量式编译器ECJ
java的后端编译器主要是各大虚拟机实现的,比如 HotSpot的**JIT编译器**
java的反编译指的是将 .class文件反向工程生成 .java文件。javap、CFR、GD-GUI是一些常用的反编译工具。因为java提供了很多例如泛型、自动装箱与拆箱等语法糖,这些语法糖在编译时候会进行desugar()解糖,这时候将解糖后的字节码文件进行反编译就可以学习这些语法糖是如何实现的。对于学习是一个很好的手段。
3. 语法糖语法糖也称为糖衣语法,指的是在编程语言中添加某种的语法。这种语法对于功能没有任何改变,但是可以增加程度的简洁性和可读性,更加方便程序员使用。语法糖是方便用户使用的,java虚拟机不支持的。因此在编译过程的第三阶段语义分析中会进行解语法糖操作将语法糖其还原成简单的语法结构。java中最常见的语法糖有泛型、变长参数、条件编译、自动拆装箱、内部类等等。我们通常可通过反编译进行语法糖背后的原理分析。
第一种糖块是Switch。java7中Switch除了基本数据类型int,char(ASCII码比较)开始支持String。因为对于编译器来说Switch只支持整形,因此String的Switch是通过hascode()方法比较哈希值和equals()方法进行安全检查防止哈希冲突实现的。
第二种糖块是泛型。不同的编译器对于泛型的处理方式不同。javac采用Code sharing方式。这种方式为每一种泛型创建一个唯一字节码并且将该泛型类型的实例都映射到这个唯一字节码上。首先将所有泛型参数用最左端的父类型替换,比如Map
第三种糖块是自动拆装箱。其中装箱指的是将byte, char, short, int, long, boolean, float, double的基本数据类型转换为对应的对象,即:Byte, Character, Integer, Long, Boolean, Float, Double。其中装箱是通过Integer.valueOf()方法实现的,而拆箱是通过intValue()方法实现的。这里要注意对象的相等比较。在java 5中在Integer中引入了在整数型区间为-128~127之间整型对象使用相同的对象引用新功能来实现缓存和重用,此时Integer a =100; Integer b=100; a==b为TRUE。
第四种糖块是方法变长参数。可变参数variable arguments是java 5引入的特性。通过(String... s)来使用。通过反编译知道他是在可变参数被使用时候创建一个对应的数组,数组长度就是调用该方法传递的实参个数,然后把参数值全部放到数组中,再将数组作为参数传递给被调用方法。
4. Java 反射反射是框架设计的灵魂
在运行状态中,对于任何一个已知名称类,都能知道这个类所有属性和方法;对于任何一个对象,都能调用他任何一个方法和属性;这种动态获取信息,动态调用对象的方法的功能称为java的反射机制。
当我们在new 一个对象 Student stu = new Student() 时候,JVM 会从磁盘寻找并加载 student.class 文件 文件到内存,同时自动创建一个 Class 对象,不同的类的实例产生的 Class 对象绝对是同一个,也就是说一个类只有一个 Class 对象。反射就是通过 Class 对象来反向获取对象的各种信息。反射可以获取:类的类型、构造方法、私有方法等等。反射将类的各种成分分别映射成一个个对象。
Java的反射是Java被看做是动态(准动态)语言的一个重要性质。反射机制允许静态语言在程序运行时候检查修改程序的结构与行为。能够在运行时加载,探知,使用编译时候完全未知的类。实际上实现了Java的 动态编译 (在运行时确定类型,绑定对象)
5. Java注解实现Java反射机制的类均位于 java.lang.reflect 包中
Class类、Array类、Field类、Method类、Construct类 分别表示成员变量、方法、构造方法
反射获得的类信息包含:modifiers (public static..) ,superClass (Object) ,interfaces(cloneable) 还有 fields,methods等等
注解可以理解为一种“标签”,注解也是一种类型,在Java 1.5 中引入使用。使用 @interface 关键字进行定义 public @interface TestAnnotation 通过 @TestAnnotation 放在类上面进行使用。
注解是一种元数据,提供数据来对程序代码进行解释,注解不是所解释代码的一部分,对程序代码的运行不产生影响。注解的作用主要有一下几点:首先他可以提供信息给编译器,在编译阶段编译器可以利用注解来探测错误、警告信息;第二点,注解可以在编译阶段为其他软件工具使用,可以生成代码、文档或其他处理;第三点,某些注解可以在运行时候接收代码的提取。(Annotation Processing Tool , APT)
元注解 是一种基本注解,能够应用到其他注解上面
6. Java 泛型 (未) 7. Java 异常类型 (未)五种元注解:
@Retention @documented @Target @Inherited @Repeatable
在反射中有:
classNotFoundException 当使用 Class.forName() 时候
NoSuchMethodException 当使用 c.getDeclaredConstructor() 时候
8. Java集合 - MapHashMap 和 TreeMap
当需要通过特定顺序对数据进行存储时候,就使用TreeMap ;HashMap 中元素的排列顺序是不固定的
根据 Ctrl+Alt+U 查看类的继承与接口实现,根据 Ctrl+Alt+B 查看源码
HashMap 适用于在Map中插入编辑 删除元素,速度会快一些;TreeMap 适合自然顺序或者自定义顺序遍历Key
- HashMapHashMap 遍历的四种方式
| 遍历方式 | map.entry | keyset |
|---|---|---|
| while | need.entrySet().iterator(); (Map.Entry) it.next() | need.keySet().iterator(); it.next |
| for | Map.Entry | need.keySet(),need.get(key) |
//01
Iterator it = need.entrySet().iterator();
while(it.hasNext()){
Map.Entry entey = (Map.Entry) it.next();
Object key = entey.getKey();
Object value = entey.getValue();
System.out.println("01: "+key+" "+value);
}
//02
for(Map.Entry entry: need.entrySet()){
char ch = entry.getKey();
int n = entry.getValue();
System.out.println("02: "+ch+" "+n);
}
//04
for(Character key: need.keySet()){
int num = need.get(key);
System.out.println("03: "+num);
}
//03
Iterator it1 = need.keySet().iterator();
while(it1.hasNext()){
Object key = it1.next();
Object value = need.get(key);
System.out.println("04: "+key+" "+value);
}
9. Java关键字
- transient
java提供了 serialization 机制,transient标记的成员变量,在java对象被序列化时候不会被包含
10. Java多线程 线程与进程 多线程实现方式 (未)代码:ArrayList_learning.java 文件
Java中多线程实现方式有:继承Thread类 和 实现 runnable接口 两种
11. 控制反转和依赖注入如果一个类A的方法的实现依赖于类B,类B称为类A的依赖。如果类A的内部实例化类B,类A和类B两者之间的出现较高的耦合度。此时若类B需要调整,那么类A内部都要跟着调整,如果这样的类过多就会出现牵一发而动全身的情况。这是需要一个第三方对类B进行控制。将类B通过构造函数、属性或者工厂模式等方法注入到类A中,这样极大程度地对类A和类B进行解耦。
这样地,将类A对类B的控制权剥离出来,交给第三方即把控制权反转给第三方,这样的思想称为 控制反转 。(IOC, Invention of Control),而控制反转的实现最典型的是通过 依赖注入 (Dependency Injection)
,类B称为类A的依赖。如果类A的内部实例化类B,类A和类B两者之间的出现较高的耦合度。此时若类B需要调整,那么类A内部都要跟着调整,如果这样的类过多就会出现牵一发而动全身的情况。这是需要一个第三方对类B进行控制。将类B通过构造函数、属性或者工厂模式等方法注入到类A中,这样极大程度地对类A和类B进行解耦。
这样地,将类A对类B的控制权剥离出来,交给第三方即把控制权反转给第三方,这样的思想称为 控制反转 。(IOC, Invention of Control),而控制反转的实现最典型的是通过 依赖注入 (Dependency Injection)



