6-1 谈谈你对Java的理解
- 平台无关性(一处编译多处运行)
- GC(垃圾回收机制)
- 语言特性(泛型、反射、lambda表达式)
- 面向对象(封装、继承、多态)
- 类库(集合、网络库、并发库、nio)
- 异常处理
6-2 平台无关性如何实现
Java 源码首先被编译成字节码,再由不同平台的 JVM 进行解析,Java 语言在不同平台上运行时不需要进行重新编译,Java 虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。

public class ByteCodeSample {
public static void main(String[] args) {
int i = 1,j = 5;
i++;
++j;
System.out.println(i);
System.out.println(j);
}
}
javac 生成 ByteCodeSample.class 字节码文件后javap -c ByteCodeSample对代码进行反汇编
Compiled from "ByteCodeSample.java"
public class com.imocc.javabasic.bytecode.ByteCodeSample {
public com.imocc.javabasic.bytecode.ByteCodeSample();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: iconst_5
3: istore_2
4: iinc 1, 1
7: iinc 2, 1
10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
13: iload_1
14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
17: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
20: iload_2
21: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
24: return
}
6-3 JVM如何加载class文件
JVM主要由Class Loader、Runtime Data Area、Execution Engine以及Native Interface这四个部分组成。它主要通过Class Loader将符合其格式要求的class文件加载到内存,并通过Execution Engine去解析class文件里的字节码并提交给操作系统去执行。

- 类加载子系统(Class Loader):依据特定格式,加载 class 文件到内存
- 执行引擎(Execution Engine):对命令进行解析
- 本地接口(Native Interface):融合不同开发语言的原生库为 Java 所用
- 运行时数据区(Runtime Data Area):JVM 内存空间结构模型
6-4 什么是反射
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的多有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。
package com.imocc.javabasic.reflect;
/**
* @author zhangjingyu
*/
public class Robot {
private String name;
public void sayHi(String helloSentence){
System.out.println(helloSentence+" "+name);
}
private String throwHello(String tag){
return "Hello "+tag;
}
}
package com.imocc.javabasic.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author zhangjingyu
* getDeclaredMethod()可以获取到公共的私有的包私有的方法(private、protected、public和default),即所有的都可以,但是不能获取到继承的,实现接口的方法。
* 私有方法必须设置 .setAccessible(true);
* getMethod() 可以获取public方法和继承的方法、实现接口的方法。
*/
public class ReflectSample {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class rc = Class.forName("com.imocc.javabasic.reflect.Robot");
Robot r = (Robot) rc.newInstance();
System.out.println("Class name is "+rc.getName());
//私有方法
Method getHello = rc.getDeclaredMethod("throwHello", String.class);
getHello.setAccessible(true);
Object str = getHello.invoke(r,"zhjynet");
System.out.println("getHello result is "+ str);
//公有方法
Method sayHi = rc.getMethod("sayHi", String.class);
sayHi.invoke(r,"Welcome");
//私有属性
Field name = rc.getDeclaredField("name");
name.setAccessible(true);
name.set(r,"Alice");
sayHi.invoke(r,"Welcome");
}
}
- 类从编译到执行的过程:
- 编译器将Robot.java源文件编译为Robot.class字节码文件
- ClassLoader将字节码转换为JVM中的Class<Robot>对象
- JVM利用Class<Robot>对象实例化为Robot对象
6-5 谈谈ClassLoader
ClassLoader再 Java 中有着非常重要的作用,它主要工作在 Class 装在的加载阶段,其主要作用是从系统外部获得 Class 二进制数据流。它使 Java 的核心组件,所有的 Class 都是由 ClassLoader 进行加载的,ClassLoader 负责通过 Class 文件里的二进制数据装载进系统,然后交给 Java 虚拟机进行连接、初始化等操作。
- ClassLoader的种类:
- BootStrapClassLoader:C++编写,加载核心库java.*
- ExtClassloader:java编写,加载扩展库javax.*(System.getProperty("java.ext.dirs")可以看到扩展库路径)
- AppClassLoader:java编写,加载程序所在目录(System.getProperty("java.class.path")可以看到加载路径,最重要的是javabasic路径)
- 自定义ClassLoader:java编写,定制化加载(关键函数,findClass、defineClass)
- 自定义 CLassLoader 的实现
- 关键函数
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; }- 具体实现
package com.imocc.javabasic.reflect; import java.io.*; /** * @author zhangjingyu */ public class MyClassLoader extends ClassLoader{ private String path; private String classLoaderName; public MyClassLoader(String path, String classLoaderName) { this.path = path; this.classLoaderName = classLoaderName; } //用于寻找类文件 @Override public Class findClass(String name){ byte[] b = loadClassData(name); return defineClass(name, b, 0, b.length); } //用于加载类文件 private byte[] loadClassData(String name){ name = path + name + ".class"; InputStream in = null; ByteArrayOutputStream out = null; try{ in = new FileInputStream(new File(name)); out = new ByteArrayOutputStream(); int i = 0; while ((i = in.read()) != -1){ out.write(i); } } catch (IOException e) { e.printStackTrace(); }finally { try { out.close(); in.close(); } catch (IOException e) { e.printStackTrace(); } } return out.toByteArray(); } }package com.imocc.javabasic.reflect; /** * @author zhangjingyu */ public class ClassLoaderChecker { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { MyClassLoader m = new MyClassLoader("/Users/zhangjingyu/Desktop/","MyClassLoader"); Class c= m.loadClass("Wail"); System.out.println(c.getClassLoader()); c.newInstance(); } } - 谈谈类加载器的双亲委派机制
- loadClass()
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } ``` - 为什么要使用双亲委派机制去加载类
- 避免多份同样字节码的加载
- 类的加载方式
- 隐式加载:new
- 显示加载:loadClass、forName等,获取到class对象之后调用newInstance()方法来生成对象实例(newInstance()不支持传入参数,需要反射getConstructor()然后调用构造器的newInstance()方法传入参数)
6-6 loadClass和forName的区别
- 类的装载过程(加载和生成实例的过程)

- loadClass和forName的区别
- Class.forName得到的class是已经完成初始化的
- Classloder.loadClass得到的class是还没有链接的
public class Robot { static { System.out.println("Hello Robot"); } }public class LoadDifference { public static void main(String[] args) throws ClassNotFoundException { //不会初始化 所以没有打印(加快加载速度,延迟加载) ClassLoader cl = Robot.class.getClassLoader(); //会初始化 所以打印了(mysql驱动就是用的这个) Class r = Class.forName("com.imocc.javabasic.reflect.Robot"); } }
6-7 Java内存模型之线程独占部分
-
内存简介

-
地址空间划分
- 内核空间
- 用户空间
-
JVM 内存模型-JDK8

- 线程私有:程序计数器、虚拟机栈、本地方法栈
- 线程共享:MetaSpace、Java 堆
-
程序计数器(Program Counter Register)
- 当前线程锁执行的字节码行号指示器(逻辑)
- 改变计数器的值来选取小一条需要执行的字节码指令
- 和线程使一对一的关系即“线程私有”
- 对 Java 方法计数,如果是 Native 方法则计数器值为 Undefined
- 不会发生内存泄露
-
Java 虚拟机栈(Stack)
- Java 方法执行的内存模型
- 包含多个栈帧
-
局部变量表和操作数栈
- 局部变量表:包含方法执行过程中的所有变量(包括this引用、所有方法参数、其他局部变量)
- 操作数栈:入栈、出栈、复制、交换、产生消费变量(在执行字节码指令过程中被用到,这种方式类似于原生cpu寄存器,大部分jvm字节码把时间花费在操作数栈的操作上,因此局部变量的数组和操作数栈的操作指令通过字节码频繁执行)
package com.imocc.javabasic.bytecode.jvm.model; /** * @author zhangjingyu */ public class ByteCodeSample { public static int add(int a,int b){ int c = 0; c =a + b; return c; } }执行 javac 生成.class 文件后执行 javap -verbose 反编译字节码
Classfile /Users/zhangjingyu/Documents/Programing/2020/2020FirstQuarter/javabasic/src/com/imocc/javabasic/bytecode/jvm/model/ByteCodeSample.class Last modified 2020-2-26; size 309 bytes MD5 checksum 16f355d5ed373660bdcc6d53fa6b3c34 Compiled from "ByteCodeSample.java" public class com.imocc.javabasic.bytecode.jvm.model.ByteCodeSample minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #3.#12 // java/lang/Object."<init>":()V #2 = Class #13 // com/imocc/javabasic/bytecode/jvm/model/ByteCodeSample #3 = Class #14 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 add #9 = Utf8 (II)I #10 = Utf8 SourceFile #11 = Utf8 ByteCodeSample.java #12 = NameAndType #4:#5 // "<init>":()V #13 = Utf8 com/imocc/javabasic/bytecode/jvm/model/ByteCodeSample #14 = Utf8 java/lang/Object { public com.imocc.javabasic.bytecode.jvm.model.ByteCodeSample(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 public static int add(int, int); descriptor: (II)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=2 0: iconst_0 1: istore_2 2: iload_0 3: iload_1 4: iadd 5: istore_2 6: iload_2 7: ireturn LineNumberTable: line 8: 0 line 9: 2 line 10: 6 } SourceFile: "ByteCodeSample.java"- descriptor:对方法的描述:有2个int参数,函数返回值为int
- flags:标记public,static
- stack=2:操作数栈,深度2
- locals:本地变量是3
- args_size:参数大小是2个
- LineNumberTable:代码的行号对应字节码行号

-
递归为什么会引发java.lang.StackOverflowError异常?
- 递归过深,栈帧数超出虚拟栈深度,解决方法就是减少递归次数或者循环替换递归
- 虚拟机栈过多会引发java.lang.OutOfMemoryError异常
- 虚拟机栈不需要GC回收,用完就会释放掉
-
本地方法栈:
- 与虚拟机栈相似,主要作用于标注了native的方法
6-8 Java内存模型之线程共享部分
-
元空间(MetaSpace)和永久代(PermGen)的区别?
-
元空间使用本地内存,而永久代使用的是 JVM 的内存
-
元空间和永久代都是存放class的相关信息,包括class和Meta、field的信息。
-
元空间和永久代都是方法区的实现,只是实现不同,所以说方法区只是JVM的一种规范。
-
-
MetaSpace相比PermGen的优势
- 字符串常量池存在永久代中,容易出现性能问题和内存溢出
- 类的方法信息大小难确定,给永久代的大小置顶带来困难(太小会出现永久代溢出,太大容易导致老年代溢出)
- 永久代会为GC带来不必要的复杂性,回收效率低(永久代中的元数据的可能会随着Full GC发生而进行移动,比较消耗虚拟机性能。HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full GC以及对以后的并发隔离类元数据等方面进行优化)
- 方便HotSpot与其他JVM如Jrockit的集成(永久代是hotspot特有的,别的VM没有永久代)
-
Java堆(Heap):
- 对象实例的分配区域(heap是个大头,占用最多的内存空间。xmx设置jvm最大可用内存)

- GC管理的主要区域(所以也称为GC堆)

- 主流的垃圾回收机制都是采用分代收集算法,所以堆可以分为新生代、老年代,再细点就有eden区、from survivor区、to survivor区。
- 对象实例的分配区域(heap是个大头,占用最多的内存空间。xmx设置jvm最大可用内存)
6-9 Java内存模型之常考题解析
-
JVM三大新能调优参数 -Xms -Xmx -Xss的含义?
- Xss:规定了每个线程虚拟机栈(堆栈)的大小(一般256k足够,此设置会影响并发线程数大小)
- -Xms:堆的初始值(就是JVM进程刚启动的时候分配的堆空间大小,后面运行时堆空间超过初始值,就会动态扩容至堆的最大空间-Xmx)
- -Xmx:堆能达到的最大值(一般Xms Xmx 都设置成一样的,因为xms不够用时动态扩容,会发生内存抖动,影响程序稳定性)
-
内存分配策略
- 静态存储:编译时确定每个数据目标再运行时的存储空间需求
- 栈式存储:数据区需求在编译时未知,运行时模块入口前确定
- 堆式存储:编译时或运行时模块入口都无法确定存储空间,需要动态分配
-
java内存模型中堆和栈的联系和区别?
- 联系:引用对象、数组时、栈里定义变量保存堆中目标的首地址

- 管理方式:栈自动释放,堆需要GC
- 空间大小:栈比堆小
- 碎片相关:栈产生的碎片远小于堆
- 分配方式:栈支持静态和动态分配,而堆仅支持动态分配
- 效率:栈的效率高于堆
- 联系:引用对象、数组时、栈里定义变量保存堆中目标的首地址
-
举例
package com.imocc.javabasic.bytecode.jvm.model; /** * @author zhangjingyu */ public class HelloWorld { private String name; public void sayHello(){ System.out.println("Hello " + name); } public void setName(String name) { this.name = name; } public static void main(String[] args) { int a = 1; HelloWorld hw = new HelloWorld(); hw.setName("test"); hw.sayHello(); } }- 元空间、堆、线程独占部分间的联系-内存角度

- 元空间、堆、线程独占部分间的联系-内存角度
-
不同JDK版本的intern()方法的区别?
https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html