第十章 早期(编译期)优化

1、Javac的源码与调试

编译期的分类:

  • 前端编译期:把*.java文件转换为*.class文件的过程。例如sun的javac、eclipseJDT中的增量编译器。
  • JIT编译期:后端运行期编译器,把字节码转换成机器骂的过程。例如 HotSpot VM的C1、C2编译器。
  • AOT编译器:静态提前编译器,直接拔Java文件编译成本地机器代码的过程,例如GCJ。

Javac的编译过程:

  • 解析与填充符号表的过程。
  • 插入式注解处理器的注解过程。
  • 分析与字节码生成的过程。
  • Javac编译动作的入口是com.sun.tools.javac.main.JavaCompiler类,上述3个过程的代码逻辑集中在这个类的compile()和compile2()方法中,其中主体代码如图所示,整个编译最关键的处理就由图中标注的8个方法来完成,下面我们具体看一下这8个方法实现了什么功能。

解析与填充符号表的过程:

  • 词法分析,是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符都可以成为标记。
  • 语法分析,是根据Token序列构造抽象语法树的过程,抽象语法树(Abstract Syntax Tree,AST)是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个阶段都代表着程序代码中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都可以是一个语法结构。
  • 填充符号表,符号表是由一组符号地址和符号信息构成的表格。符号表中所登记的信息在编译的不同阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检查(如检查一个名字的使用和原先的说明是否一致)和产生中间代码。在目标代码生成阶段,当对符号名进行地质分配时,符号表是地址分配的依据。填充符号表的过程由com.sun.tools.javac.comp.Enter类实现,此过程的出口是一个待处理列表(To Do List),包含了每一个编译单元的抽象语法树的顶级节点,以及package-info.java(如果存在的话)的顶级节点

注解处理器:

  • 在JDK1.6中实现了JSR-269规范,提供了一组插入式注解处理器的标准API在编译期间对注解进行处理,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止。
  • 插入式注解处理器的初始化过程是在initPorcessAnnotations()方法中完成的,而它的执行过程则是在processAnnotations()方法中完成的。
  • 实现注解处理器的代码需要继承抽象类javax.annotation.processing.AbstractProcessor,这个抽象类中只有一个必须覆盖的abstract方法:“process()”,除了process()方法的传入参数之外,还有一个很常用的实例变量“processingEnv”,它是AbstractProcessor中的一个protected变量,在注解处理器初始化的时候(init()方法执行的时候)创建,继承了AbstractProcessor的注解处理器代码可以直接访问到它。它代表了注解处理器框架提供的一个上下文环境,要创建新的代码、向编译器输出信息、获取其他工具类等都需要用到这个实例变量。注解处理器除了process()方法及其参数之外,还有两个可以配合使用的Annotations:@SupportedAnnotationTypes和@SupportedSourceVersion,前者代表了这个注解处理器对哪些注解感兴趣,可以使用星号“*”作为通配符代表对所有的注解都感兴趣,后者指出这个注解处理器可以处理哪些版本的Java代码。每一个注解处理器在运行的时候都是单例的,如果不需要改变或生成语法树的内容,process()方法就可以返回一个值为false的布尔值,通知编译器这个Round中的代码未发生变化,无须构造新的JavaCompiler实例,在这次实战的注解处理器中只对程序命名进行检查,不需要改变语法树的内容,因此process()方法的返回值都是false。
    1. package com.ecut.javac;
    2.  
    3. import java.util.EnumSet;
    4. import java.util.Set;
    5.  
    6. import javax.annotation.processing.AbstractProcessor;
    7. import javax.annotation.processing.Messager;
    8. import javax.annotation.processing.ProcessingEnvironment;
    9. import javax.annotation.processing.RoundEnvironment;
    10. import javax.annotation.processing.SupportedAnnotationTypes;
    11. import javax.annotation.processing.SupportedSourceVersion;
    12. import javax.lang.model.SourceVersion;
    13. import javax.lang.model.element.Element;
    14. import javax.lang.model.element.ElementKind;
    15. import javax.lang.model.element.ExecutableElement;
    16. import javax.lang.model.element.Name;
    17. import javax.lang.model.element.TypeElement;
    18. import javax.lang.model.element.VariableElement;
    19. import javax.lang.model.util.ElementScanner7;
    20. import javax.tools.Diagnostic.Kind;
    21.  
    22. //这个注解处理器对那些注解感兴趣,使用*表示支持所有的Annotations
    23. @SupportedAnnotationTypes(value = "*")
    24. //这个注解处理器可以处理那些Java版本的代码,只支持Java1.8的代码
    25. @SupportedSourceVersion(value = SourceVersion.RELEASE_8)
    26. public class NameCheckProcessor extends AbstractProcessor {
    27.  
    28. private NameCheck nameCheck;
    29.  
    30. /**
    31. * 初始化检查插件
    32. * 继承了AbstractProcessor的注解处理器可以直接访问继承了processingEnv,它代表上下文环境,要穿件新的代码、向编译器输出信息、获取其他工具类都需要用到这个实例
    33. *
    34. * @param processingEnv ProcessingEnvironment
    35. */
    36. @Override
    37. public synchronized void init(ProcessingEnvironment processingEnv) {
    38. super.init(processingEnv);
    39. this.nameCheck = new NameCheck(processingEnv);
    40. }
    41.  
    42. /**
    43. * 对语法树的各个节点今夕名称检查
    44. * java编译器在执行注解处理器代码时要调用的过程
    45. *
    46. * @param annotations
    47. * @param roundEnv
    48. * @return
    49. */
    50. @Override
    51. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    52. if (!roundEnv.processingOver()) {
    53. for (Element element : roundEnv.getRootElements()) {
    54. nameCheck.check(element);
    55. }
    56. }
    57. return false;
    58. }
    59.  
    60. /**
    61. * 程序名称规范的编译期插件
    62. * 程序名称规范的编译器插件 如果程序命名不合规范,将会输出一个编译器的Warning信息
    63. *
    64. * @author kevin
    65. */
    66. public static class NameCheck {
    67. Messager messager = null;
    68. public NameCheckScanner nameCheckScanner;
    69.  
    70. private NameCheck(ProcessingEnvironment processingEnv) {
    71. messager = processingEnv.getMessager();
    72. nameCheckScanner = new NameCheckScanner(processingEnv);
    73. }
    74.  
    75. /**
    76. * 对Java程序明明进行检查,根据《Java语言规范(第3版)》6.8节的要求,Java程序命名应当符合下列格式:
    77. * <ul>
    78. * <li>类或接口:符合驼式命名法,首字母大写。
    79. * <li>方法:符合驼式命名法,首字母小写。
    80. * <li>字段:
    81. * <ul>
    82. * <li>类,实例变量:符合驼式命名法,首字母小写。
    83. * <li>常量:要求全部大写
    84. * </ul>
    85. * </ul>
    86. *
    87. * @param element
    88. */
    89. public void check(Element element) {
    90. nameCheckScanner.scan(element);
    91. }
    92.  
    93. /**
    94. * 名称检查器实现类,继承了1.6中新提供的ElementScanner6<br>
    95. * 将会以Visitor模式访问抽象语法数中得元素
    96. *
    97. * @author kevin
    98. */
    99. public static class NameCheckScanner extends ElementScanner7<Void, Void> {
    100. Messager messager = null;
    101.  
    102. public NameCheckScanner(ProcessingEnvironment processingEnv) {
    103. this.messager = processingEnv.getMessager();
    104. }
    105.  
    106. /**
    107. * 此方法用于检查Java类
    108. */
    109. @Override
    110. public Void visitType(TypeElement e, Void p) {
    111. scan(e.getTypeParameters(), p);
    112. checkCamelCase(e, true);
    113. super.visitType(e, p);
    114. return null;
    115. }
    116.  
    117. /**
    118. * 检查方法命名是否合法
    119. */
    120. @Override
    121. public Void visitExecutable(ExecutableElement e, Void p) {
    122. if (e.getKind() == ElementKind.METHOD) {
    123. Name name = e.getSimpleName();
    124. if (name.contentEquals(e.getEnclosingElement().getSimpleName())) {
    125. messager.printMessage(Kind.WARNING, "一个普通方法:" + name + " 不应当与类名重复,避免与构造函数产生混淆", e);
    126. checkCamelCase(e, false);
    127. }
    128. }
    129. super.visitExecutable(e, p);
    130. return null;
    131. }
    132.  
    133. /**
    134. * 检查变量是否合法
    135. */
    136. @Override
    137. public Void visitVariable(VariableElement e, Void p) {
    138. /* 如果这个Variable是枚举或常量,则按大写命名检查,否则按照驼式命名法规则检查 */
    139. if (e.getKind() == ElementKind.ENUM_CONSTANT || e.getConstantValue() != null || heuristicallyConstant(e)) {
    140. checkAllCaps(e);
    141. } else {
    142. checkCamelCase(e, false);
    143. }
    144. super.visitVariable(e, p);
    145. return null;
    146. }
    147.  
    148. /**
    149. * 判断一个变量是否是常量
    150. *
    151. * @param e
    152. * @return
    153. */
    154. private boolean heuristicallyConstant(VariableElement e) {
    155. if (e.getEnclosingElement().getKind() == ElementKind.INTERFACE) {
    156. return true;
    157. } else if (e.getKind() == ElementKind.FIELD && e.getModifiers().containsAll(EnumSet.of(javax.lang.model.element.Modifier.FINAL, javax.lang.model.element.Modifier.STATIC, javax.lang.model.element.Modifier.PUBLIC))) {
    158. return true;
    159. }
    160. return false;
    161. }
    162.  
    163. /**
    164. * 检查传入的Element是否符合驼式命名法,如果不符合,则输出警告信息
    165. *
    166. * @param e
    167. * @param initialCaps
    168. */
    169. private void checkCamelCase(Element e, boolean initialCaps) {
    170. String name = e.getSimpleName().toString();
    171. boolean previousUpper = false;
    172. boolean conventional = true;
    173. int firstCodePoint = name.codePointAt(0);
    174. if (Character.isUpperCase(firstCodePoint)) {
    175. previousUpper = true;
    176. if (!initialCaps) {
    177. messager.printMessage(Kind.WARNING, "名称:" + name + " 应当已小写字符开头", e);
    178. return;
    179. }
    180. } else if (Character.isLowerCase(firstCodePoint)) {
    181. if (initialCaps) {
    182. messager.printMessage(Kind.WARNING, "名称:" + name + " 应当已大写字母开否", e);
    183. return;
    184. }
    185. } else {
    186. conventional = false;
    187. }
    188. if (conventional) {
    189. int cp = firstCodePoint;
    190. for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) {
    191. cp = name.codePointAt(i);
    192. if (Character.isUpperCase(cp)) {
    193. if (previousUpper) {
    194. conventional = false;
    195. break;
    196. }
    197. previousUpper = true;
    198. } else {
    199. previousUpper = false;
    200. }
    201. }
    202. }
    203. if (!conventional) {
    204. messager.printMessage(Kind.WARNING, "名称:" + name + "应当符合驼式命名法(Camel Case Names)", e);
    205. }
    206. }
    207.  
    208. /**
    209. * 大写命名检查,要求第一个字符必须是大写的英文字母,其余部分可以下划线或大写字母
    210. *
    211. * @param e
    212. */
    213. private void checkAllCaps(VariableElement e) {
    214. String name = e.getSimpleName().toString();
    215. boolean conventional = true;
    216. int firstCodePoint = name.codePointAt(0);
    217. if (!Character.isUpperCase(firstCodePoint)) {
    218. conventional = false;
    219. } else {
    220. boolean previousUnderscore = false;
    221. int cp = firstCodePoint;
    222. for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) {
    223. cp = name.codePointAt(i);
    224. if (cp == (int) '_') {
    225. if (previousUnderscore) {
    226. conventional = false;
    227. break;
    228. }
    229. previousUnderscore = true;
    230. } else {
    231. previousUnderscore = false;
    232. if (!Character.isUpperCase(cp) && !Character.isDigit(cp)) {
    233. conventional = false;
    234. break;
    235. }
    236. }
    237.  
    238. }
    239. }
    240. if (!conventional) {
    241. messager.printMessage(Kind.WARNING, "常量:" + name + " 应该全部以大写字母" + "或下划线命名,并且以字符开否", e);
    242. }
    243. }
    244. }
    245. }
    246.  
    247. }

    测试类:

    1. package com.ecut.javac;
    2.  
    3. public class BADLY_NAME_CODE {
    4. enum colors {
    5. red, blue, green;
    6. }
    7.  
    8. static final int _FORTy_TWO = 42;
    9. public static int NOT_A_CONSTANT = _FORTy_TWO;
    10.  
    11. protected void Badly_Named_Code() {
    12. return;
    13. }
    14.  
    15. public void NOTcameCASEmethodNAME() {
    16. return;
    17. }
    18. }

    运行结果如下图:

语义分析与字节码生成:

  • 语法分析之后,编译器获得了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的。而语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,如进行类型审查。
  • 语义分析过程分为标注检查以及数据及控制流分析两个步骤。字节码生成之前还需要解语法糖。
  • 标注检查,步骤检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。标注检查步骤在Javac源码中的实现类是com.sun.tools.javac.comp.Attr和com.sun.tools.javac.comp.Check类。
  • 数据及控制流分析,是对程序上下文逻辑更进一步的验证,它可以检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题。
  • 解语法糖, 语法糖(System Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说,使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。java中常用的语法糖主要是泛型、变长参数、自动装箱/拆箱等,虚拟机运行时不支持这些语法,他们在编译阶段还原回简单的基础语法结构,这个过程称为解语法糖。
  • 字节码生成,是javac编译过程的最后一个阶段,这个阶段不仅把前面各个步骤所生成的信息(语法树、符号表)转化成字节码写到磁盘里,编译器还进行了少量的代码添加和转换工作。 比如:实力构造器()方法和类构造器()方法就是在这个阶段添加到语法树中(并不是默认构造函数,如何用户代码中没有任何构造函数,添加默认构造函数是在填充符号表阶段完成)

2、Java语法糖的味道

泛型与类型檫除:

  • Java中的泛型只在程序源码中存在,在编译后的字节码文件中就已经替换为原来的原生类型(裸类型),并且在相应的地方插入强制转型代码。
  • 因此对于运行期的java语言来说,ArrayList<Integer>与ArrayList<String>就是同一个类,所以泛型技术就是一颗语法糖,java语言中的泛型实现方法称为类型檫除,基于这种方法实现的泛型称为伪泛型。

    泛型擦除前:

    1. package com.ecut.javac;
    2.  
    3. import java.util.HashMap;
    4. import java.util.Map;
    5.  
    6. public class GenericTest {
    7.  
    8. public static void main(String[] args) {
    9. Map< String , String > map = new HashMap<>();
    10.  
    11. map.put("How are you ?","吃了吗?");
    12.  
    13. map.put("Hi","您好!");
    14.  
    15. System.out.println(map.get("Hi"));
    16. }
    17. }

    编译后,泛型擦除后:

    1. package com.ecut.javac;
    2.  
    3. import java.io.PrintStream;
    4. import java.util.HashMap;
    5. import java.util.Map;
    6.  
    7. public class GenericTest
    8. {
    9. public static void main(String[] args)
    10. {
    11. Map map = new HashMap();
    12.  
    13. map.put("How are you ?", "吃了吗?");
    14.  
    15. map.put("Hi", "您好!");
    16.  
    17. System.out.println((String)map.get("Hi"));
    18. }
    19. }

    泛型重载:

    1. package com.ecut.javac;
    2.  
    3. import java.util.List;
    4.  
    5. public class GenericTypes {
    6.  
    7. //报错信息:“method(list<string>)”与“method(list<integer>)”冲突;两种方法具有相同的擦除功能
    8. public static void method(List<String> list ){
    9. System.out.println("invoke method(List<String> list ");
    10. }
    11.  
    12. public static void method(List<Integer> list ){
    13. System.out.println("invoke method(List<Integer> list ");
    14. }
    15. }
  • Signature属性存储一个方法字节码层面的特征签名(区别java层面特征签名,还包含返回值和受查异常表),这个属性中保存的参数类型并不是原生的类型,而是参数化的类型信息。
  • LocalVariableTable属性用来描述栈帧中局部变量与Java源码中定义的变量之间的关系。
  • Class文件的Signature参数檫除类型只是檫除Code属性中的字节码,实际上元数据还保留了泛型信息。

自动装箱、拆箱与遍历循环:

  • 自动装箱、拆箱与遍历循环解语法糖测试

    1. package com.ecut.javac;
    2.  
    3. import java.util.Arrays;
    4. import java.util.List;
    5. import java.util.stream.Collectors;
    6. import java.util.stream.Stream;
    7.  
    8. public class ForeachTest {
    9. public static void main(String[] args) {
    10. List<Integer> list = Arrays.asList(1, 2, 3, 4);
    11. // JDK1.8
    12. List<Integer> list2 = Stream.of(1, 2, 3, 4).collect(Collectors.toList());
    13. // JDK1.9
    14. //List<Integer> list3 = Lists.newArrayList(1, 2, 3, 4);
    15. int sum = 0;
    16. for (int i : list) {
    17. sum += i;
    18. }
    19. System.out.println(sum);
    20. }
    21. }

    编译后:

    1. package com.ecut.javac;
    2.  
    3. import java.io.PrintStream;
    4. import java.util.Arrays;
    5. import java.util.Iterator;
    6. import java.util.List;
    7. import java.util.stream.Collectors;
    8. import java.util.stream.Stream;
    9.  
    10. public class ForeachTest
    11. {
    12. public static void main(String[] args)
    13. {
    14. List list = Arrays.asList(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) });
    15.  
    16. List list2 = (List)Stream.of(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) }).collect(Collectors.toList());
    17.  
    18. int sum = 0;
    19. for (Iterator localIterator = list.iterator(); localIterator.hasNext(); ) { int i = ((Integer)localIterator.next()).intValue();
    20. sum += i;
    21. }
    22. System.out.println(sum);
    23. }
    24. }

    泛型在编译过程中会进行擦除,将泛型参数去除;自动装箱、拆箱在变之后被转化成了对应的包装盒还原方法,如Integer.valueOf()与Integer.intValue()方法;而遍历循环则被还原成了迭代器的实现,这也是为什么遍历器循环需要被遍历的类实现Iterator接口的原因。变长参数(asList()),它在调用的时候变成了一个数组类型的参数,在变长参数出来之前,程序员使用数组来完成类似功能。

  • 自动装箱的陷阱
    1. package com.ecut.javac;
    2.  
    3. /**
    4. * 基本数据类型和引用类型的区别主要在于基本数据类型是分配在栈上的,而引用类型是分配在堆上的
    5. * 不论是基本数据类型还是引用类型,他们都会先在栈中分配一块内存,对于基本类型来说,这块区域包含的是基本类型的内容;
    6. * 而对于引用类型来说,这块区域包含的是指向真正内容的指针,真正的内容被手动的分配在堆上。
    7. */
    8. public class AutoBox {
    9. public static void main(String[] args) {
    10. Integer a = 1;
    11. Integer b = 2;
    12. Integer c = 3;
    13. Integer d = 3;
    14. Integer e = 321;
    15. Integer f = 321;
    16. Long g = 3L;
    17. Integer h = new Integer(3);
    18. Integer i = new Integer(3);
    19. /*
    20. 包装类遇到“==”号的情况下,如果不遇到算数运算符(+、-、*、……)是不会自动拆箱的.所以这里“==”比较的是对象(地址)
    21. */
    22. //true 对于Integer 类型,整型的包装类系统会自动在常量池中初始化-128至127的值,如果c和d都指向同一个对象,即同一个地址。
    23. System.out.println("c==d:" + (c == d));
    24. //false 但是对于超出范围外的值就是要通过new来创建包装类型,所以内存地址也不相等
    25. System.out.println("e==f:" + (e == f));
    26. //true 因为遇到运算符自动拆箱变为数值比较,所以相等。
    27. System.out.println("c==(a+b):" + (c == (a + b)));
    28. //true 包装类都重写了equals()方法,他们进行比较时是比的拆箱后数值。但是并不会进行类型转换
    29. System.out.println("c.equals(a+b)" + (c.equals(a + b)));
    30. //true ==遇到算数运算符会自动拆箱(long) 3==(int)3
    31. System.out.println("g==(a+b)" + (g == (a + b)));
    32. //false equals首先看比较的类型是不是同一个类型,如果是,则比较值是否相等,否则直接返回false
    33. System.out.println("g.equals(a+b):" + g.equals(a + b));
    34. //true equals首先看比较的类型是不是同一个类型,如果是,则比较值是否相等,否则直接返回false
    35. System.out.println("h.equals(i):" + h.equals(i));
    36. //false 通过new来创建包装类型,所以内存地址也不相等
    37. System.out.println("h == i:" + (h == i));
    38. }
    39. }

条件编译:

  • java语言之中并没有使用预处理器,因为Java语言天然的编译方式(编译器并非一个个地编译Java文件,而是将所有编译单元的语法树顶级节点输入到待处理列表后再进行编译,因此各个文件之间能够互相提供符号信息)无须使用预处理器。
  • Java语言可以使用条件为常量的if语句进行条件编译,根据常量的真假来将分支中不成立的代码块消除掉。
  • 只能使用if,若使用常量甚至于其他带有条件判断能力的语句描述搭配,则可能在控制流分析中提示错误,拒绝编译Uncreachable Code。
    1. package com.ecut.javac;
    2.  
    3. public class IfTest {
    4. public static void main(String[] args) {
    5. if(true){
    6. System.out.println("true");
    7. }else{
    8. System.out.println("false");
    9. }
    10. }
    11. }

    编译后:

    1. package com.ecut.javac;
    2.  
    3. import java.io.PrintStream;
    4.  
    5. public class IfTest
    6. {
    7. public static void main(String[] args)
    8. {
    9. System.out.println("true");
    10. }
    11. }

源码地址:

https://github.com/SaberZheng/jvm-test/tree/master/src/com/ecut/javac

转载请于明显处标明出处:

https://www.cnblogs.com/AmyZheng/p/10555363.html

《深入理解java虚拟机》读书笔记九——第十章的更多相关文章

  1. 深入理解Java虚拟机 -- 读书笔记(1):JVM运行时数据区域

    深入理解Java虚拟机 -- 读书笔记:JVM运行时数据区域 本文转载:http://blog.csdn.net/jubincn/article/details/8607790 本系列为<深入理 ...

  2. 【Todo】深入理解Java虚拟机 读书笔记

    有一个在线系列地址 <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> http://book.2cto.com/201306/25426.html 已经下载了这本书(60多M ...

  3. 深入理解Java虚拟机读书笔记5----虚拟机字节码执行引擎

    五 虚拟机字节码执行引擎   1 运行时栈帧结构     ---栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素.     ---栈帧中存储了方法的局部变 ...

  4. 深入理解Java虚拟机读书笔记8----Java内存模型与线程

    八 Java内存模型与线程   1 Java内存模型     ---主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.     ---此处的变量和J ...

  5. 深入理解Java虚拟机读书笔记7----晚期(运行期)优化

    七 晚期(运行期)优化 1 即时编译器(JIT编译器)     ---当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”,包括被多次调用的方法和被多次执行的循环体.     ...

  6. 深入理解Java虚拟机读书笔记4----虚拟机类加载机制

    四 虚拟机类加载机制 1 类加载机制     ---概念:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型.     -- ...

  7. 深入理解Java虚拟机读书笔记3----类文件结构

    三 类文件结构 1 Java虚拟机的两种中立特性     · 平台无关性     · 语言无关性     实现平台无关性和语言无关性的基础是虚拟机和字节码存储格式(Class文件).   2 Clas ...

  8. 深入理解Java虚拟机读书笔记1----Java内存区域与HotSpot虚拟机对象

    一 Java内存区域与HotSpot虚拟机对象 1 Java技术体系.JDK.JRE?     Java技术体系包括:         · Java程序设计语言:         · 各种硬件平台上的 ...

  9. 深入理解java虚拟机读书笔记--java内存区域和管理

    第二章:Java内存区域和内存溢出异常 2.2运行时数据区域 运行时数据区分为方法区,堆,虚拟机栈,本地方法栈,程序计数器 方法区和堆是线程共享的区域 虚拟机栈,本地方法栈,程序计数器是数据隔离的数据 ...

  10. 深入理解java虚拟机读书笔记1--java内存区域

    Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随 ...

随机推荐

  1. Android9.0 Settings 修改踩坑记录

    问题现象 上图展示的很清楚,当系统语言为中文时,PreferenceScreen 中的折叠项 summary 描述重复显示的 bug,系统语言为英文时正常. 修改历程 先搜索 当前显示了 字符串,还真 ...

  2. net start MySQL57 MySQL57 服务正在启动 . MySQL57 服务无法启动。

    造成这种情况的原因有很多,如果直接百度错误信息的话,不一定能很快解决问题,所以,出现这种情况,我们可以使用 mysqld --console 命令来查看报错信息,然后根据报错信息来百度,这样就很快定位 ...

  3. 教你用python爬虫监控教务系统,查成绩快人一步!

    教你用python爬虫监控教务系统,查成绩快人一步!这几天考了大大小小几门课,教务系统又没有成绩通知功能,为了急切想知道自己挂了多少门,于是我写下这个脚本. 设计思路:设计思路很简单,首先对已有的成绩 ...

  4. Html介绍,认识head标签

    <head></head>标签位于html文档的头部,主要是用来描述文档的各种属性和信息,包括文档的标题等,当然文档头部包含的数据都不会真正作为内容展示给访客的. 如下的一些标 ...

  5. postgresql开篇

    postgresql 作为官方号称的最先进的开源数据库,从今天(2020-1-19)起开始系统的学习一下,记录自己学习的点点滴滴.

  6. 小白的linux笔记7:批量运行复杂的linux命令组合——BASH简单使用法

    linux的BASH就相当于windows下的BAT文件,可以批处理命令.比如写好一个python脚本后,需要在运行时候加参数,但这个参数又不想每次输入,就可以用BASH的方式写好整条命令,然后直接运 ...

  7. mysql在node中的一些操作

    mysql 服务: a) 安装wamp|xamp 开启 mysql服务 b) 安装mysql 开启服务 库操作: 客户端:软件操作(UI工具) wamp的客户端是phpmyadmin navicat ...

  8. w13scan扫描器的使用

    0x01 w13scan第三方包下载 环境:python3以上 下载:pip install w13scan 0x02 利用w13scan API接口编写w13scan.py from W13SCAN ...

  9. PTA 凑零钱(深度优先搜索)

    韩梅梅喜欢满宇宙到处逛街.现在她逛到了一家火星店里,发现这家店有个特别的规矩:你可以用任何星球的硬币付钱,但是绝不找零,当然也不能欠债.韩梅梅手边有 10000 枚来自各个星球的硬币,需要请你帮她盘算 ...

  10. 数据库MySQL之存储过程

    存储过程的定义 存储过程是一种在数据库中存储复杂程序,以便外部程序调用的一种数据库对象.其在思想上与面向对象编程中函数的定义与调用一致,存储过程只是SQL语言维度上的封装与运用. 存储过程的优缺点 优 ...