目录
6-1 谈谈你对Java的理解
- 平台无关性,就是一次编译到处运行;
- 垃圾回收机制。正因为有了GC,我们在用Java程序开发的时候就不必像C++那样去手动的清理内存啦;
- Java语言特性。包括泛型、反射、lambda表达式;
- 面向对象。其中包括了封装,继承,还有多态;
- Java本身类库。包括集合、并发库、网络库以及IO流;
- 异常处理
6-2 平台无关性如何实现
编译时
- 编译时用到javac指令,编译的是java的源码,将源码编译生成字节码,并存入到对应的class文件中此文件中还会添加一个共有的静态常量属性.class,这个属性记录了类的相关信息即类型信息是class的一个实例。
Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令
为什么JVM不直接将源码解析成机器码去执行
- 准备工作:每次执行都需要各种检查,重复性的工作
- 兼容性: 也可以将别的语言解析成字节码
6-3 JVM如何加载Class文件
JVM内存结构
- Class Loader加载编译好的.class文件到内存,符合格式要求的文件就能加载
- Execution Engine对命令进行解析
- Native Interface融合不同的开发语言的原生库为java所用
- Runtime Data Area:JVM内存空间结构模型
6-4 什么是反射
理论的回答
- Java反射机制是在运行状态中,对于任意一个类,都能够直到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象房啊二点功能成为Java语言的反射机制。
获取Class对象的三种方式:
- Object ——>getClass();
- 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
- 通过Class类的静态方法:forName(String className)(常用)
- 强调]:在运行期间,一个类,只有一个与之相对应的Class对象产生。
反射机制的作用
- 在运行时判断任意一个对象所属的类;
- 在运行时获取类的对象;
- 在运行时访问Java对象的属性,方法,构造方法等;
如何获取类的构造方法并使用
- 批量的方法
- public Constructor[] getConstructors():所有"公有的"构造方法
- public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
- 获取单个的方法,并调用:
- public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
- public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有、
- 调用构造方法:
- Constructor-->newInstance(Object... initargs)
反射之获取并操作成员变量
- getDeclaredField(String name)----获取指定name名称的(包含private修饰)字段,不包括继承的字段;
- getDeclaredField()----获取Class对象所表示的类或接口的所有(包含private修饰)字段,不包括继承的字段;
- getField(String name)----获取指定name名称、具有public修饰的字段,包含继承字段;
- getField()----获取修饰符为public的字段,包含继承字段;
反射之获取并操作成员方法
- 批量的:
- public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
- 获取单个的:
- public Method getMethod(String name,Class<?>... parameterTypes):
- public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
- 调用方法:
- Method --> public Object invoke(Object obj,Object... args):
6-5 谈谈ClassLoader
类从编译到执行的过程
- 编译器将Robot.java源文件编译为Robot.class字节码文件
- ClassLoader将字节码转换为JVM中的Class<Robot>对象
- JVM利用Class<Robot>对象实例化为Robot对象
谈谈ClassLoader
概述
- 主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里二点二进制数据流装载进系统,然后交给JVM进行连接初始化等
ClassLoader种类
- BootStrapClassLoader:C++编写,加载核心类库java.*
- ExtClassLoader:Java编写,加载扩展类库javax.*
- AppClassLoader:java编写,加载程序所在目录
- 自定义Class Loader:Java编写,定制化加载
总结:
- 只要是传入的二进制流合法,我们就能通过不同的形式进行加载。比如findClass通过远程网络访问二进制流,并生成类;也可以对某些敏感的class类进行加密,并在findClass中进行解密;还可以对生成的二进制流进行处理,给类添加一些信息等,如ASM(字节码增强技术)就是用来改进二进制流的。还可以延申去思考AOP的实现!
6-6 ClassLoader的双亲委派机制
双亲委派机制 代码还没有看
- 在加载一个类时,会通过递归的方法,先看自定义类加载器中是否已经加载过了,如果没有,就会调用父类加载器AppClassLoader的loadClass()方法看是否加载过,还没加载过就会继续调用父类加载器ExtClassLoader看是否加载过,直到BootStrapClassLoader。如果都没加载过,就会考虑自己能不能加载,如果自己不能加载,就会下沉到子类加载器中去加载,如果一直都加载不了,就会抛出类找不到的异常。双亲委派机制可以保证同一份字节码文件只会被加载一次,并且可以保证一些核心的类不会被人篡改替换,保证安全性。
为什么要使用双亲委派机制去加载类
- 避免多份同样字节码被多个classloader重复加载,节省内存
6-7 loadClass和forName的区别
类的加载方式
- 隐式加载:new 对象
- 显示加载:① loadClass 方法;② forName 方法
- 通过显示加载,获得的是Class类,还需要通过newInstance方法来生成实例对象。
- newInstance方法还不支持传入形参,new的方式是支持的。如果想要用newInstance传入形参,还得通过反射来获取类的加载器。
了解类的加载过程 代码解析(还没看)
- 加载 ---> 链接(校验,准备,解析)---> 初始化
- >加载:加载字节码文件,生成Class对象
- >链接
- 校验--检查加载的class的正确性和安全性
- 准备--为类变量空间分配存储空间并设置类变量初始值
- 解析---JVM将常量池内的符号引用转换为直接引用
- >初始化: 类变量赋值/执行静态代码块
两种方法的区别应用
- 示例1:JDBC中Driver加载时,必须使用forName方法。观察源码后发现,Driver中有一段静态代码块,用来创建数据库驱动,必须执行。
- 示例2:Spring IOC 中,资源加载器获取要加载的资源时,即读取Bean时,为了提高效率,不会堆类进行初始化,这里用到的类加载方式就是使用loadClass进行加载。这就涉及到了Spring IOC中的延迟加载lazing Loading 了,Spring IOC大量使用了延时加载,只有在实际使用这个类时,才会进行初始
6-8 Java内存模型之线程独占部分
线程角度
- 线程私有: 程序计数器、虚拟机栈、本地方法栈
- 线程共有:元空间属于方法区的实现、Java堆
程序计数器
- 这是一个“逻辑”而非“物理”计数器 .
- 内存小,不易发生泄漏 .
- 线程独占,与线程一一对应 .
- 只为Java方法计数,native方法值为undefine .
Java虚拟机栈
- Java方法执行 的内存模型
- 包含多个栈帧
- 局部变量表:包含方法执行过程中的所有变量。
- 操作数栈执行字节码指令过程中被用到
- 局部变量表为操作数栈提供必要的数据支撑。
- 虚拟机栈是线程隔离的即每个线程都有自己独立的虚拟机栈(可以说一个线程就代表一个栈帧)
- StackOverflowError 单个线程请求产生的栈帧数已经超过了栈的最大深度
- OutOfMemoryError 虚拟机栈内存耗尽,堆中无法再申请到新的内存时抛出的异常
6-9 Java内存模型之线程共享部分
元空间(MetaSpace)与永久代(PermGet)的区别
- 元空间在JDK8以后开始把类的元数据放在本地堆内存中,这块区域就叫MetaSpace。
- MetaSpace在JDK7及以前是属于永久代的,元空间和永久代都是存取class的相关信息,包括class对象的method、filed等。
- 元空间与永久代都是方法区的实现。
- 元空间7之后字符串常量池被移到了java堆中,8之后元空间替代了永久代。
- 元空间使用的是本地内存,而永久代使用的是JVM的内存。
Java堆(heap)
- 对象实例的分配区域
- GC管理的主要区域
- 新生代
- Eden
- from Survivor1, to survivor
- 老年代
- 新生代
6-10 Java内存模型之 常考题分析-1
JVM三大性能调优参数-Xms -Xmx -Xss的含义
- -Xss --- 规定了每个线程虚拟机栈(堆栈)的大小,一般情况下,256k,这个配置将会影响此配置中并发线程数的大小
- -Xms ---初始的Java堆的大小,进程刚创建出来的时候,专属Java堆的大小 ,一旦对象容量超过了Java堆的初始大小,那么将会自动进行扩容,扩容到-Xss的大小
- -Xmx --- 堆能达到的最大值
- 一般将-Xms -Xmx这两个值设置成是一样的,当haep不够用,发生扩容的时候,会发生内存抖动,影响程序运行的稳定性
Java内存模型中栈和堆的区别---内存分配策略
程序运行的时候,有三种内存分配策略,静态的,栈式的,堆式的
- 静态存储---编译时确定每个数据目标在运行时的存储空间需求,要求程序代码中不允许有可变数据结构的存在,也不允许有嵌套和递归的记过存在,这些都会导致编译程序无法计算准确的存储空间
- 栈式存储---可称为动态的存储分配,程序对数据区需求在编译时未知,运行时数据模块入口前确定,但是在运行中进入一个程序模块的时候,必须直到该程序模块所需要的数据的大小,才能分配其内存。和栈存储一样,先进后出的原则
- 堆式存储---负责程序编译时或者运行时都无法确定存储要求的数据结构的内存分配,比如可变常数串和对象实例。堆中内存可以按照任意顺序分配和释放。
Java内存模型中栈和堆的区别
- 联系---引用对象、数组的时候,栈里定义变量保存堆中目标的首地址,栈中的这个变量就成了数组或者对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问队中的数组或者对象。引用变量在作用域之外就会被释放掉,而数组本身在堆中分配,即使程序运行到使用new创建的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,在没有引用变量指向的时候,才会变成垃圾,然后等待一个不确定的时间,被垃圾回收器释放掉。
- 区别
- 管理方式:栈自动释放,堆需要GC
- 空间大小:栈比堆小
- 碎片相关:栈产生的碎片远小于堆
- 分配方式:栈支持静态和动态分配,而堆仅支持动态分配
- 效率:栈的效率比堆高
元空间、堆、线程独占部分空间的联系-内存角度
不同jdk下intern方法的表现????
- JDK6 的intern()会把堆中的字符串的副本放入常量池中,堆和常量池的地址是不同的。
- JDK6+ 的intern()把副本放入常量池的同时也会把该引用变成堆中对象的地址