Created with Sketch.

Java 标签下的文章 共 2 篇

目录 Java 内存区域 JVM 垃圾回收 类加载器与类加载器 HotSpot 虚拟机对象实现 一、Java 内存区域 程序计数器 当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。 虚拟机栈 除Native方法外,所有的方法都是通过栈来实现的。方法调用的数据需要通过栈进行传递,每次方法调用,都会有一个对应的栈帧被压入栈中,每次方法调用结束,都会有一个栈帧被弹出。栈的生命周期也和线程相同。 局部变量表:存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)。 操作数栈:作为方法调用的中转站,用于存放方法执行过程中的临时操作数和中间计算结果。由于JVM的无寄存器设计,操作数栈成为核心的计算区域。例如: 动态链接:指向运行时常量池中的方法引用(Method References)。用于在方法中调用其他方法时,将Class文件常量池中指向方法的符号引用(因为编译期无法知道实际内存地址)转化为其在内存地址中的直接引用。 程序运行中栈可能会出现两种错误: StackOverFlowError:栈的内存大小不允许动态扩展时,线程请求栈的深度超过当前虚拟机栈最大深度(如函数调用陷入无限循环)。 OutOfMemoryError:栈的内存大小允许动态扩展时,虚拟机在动态扩展栈时无法申请到足够的内存空间。 本地方法栈 与虚拟机栈类似,区别在于其为Native方法服务,HotSpot中与虚拟机栈合二为一。 堆 在JVM启动时创建,是由所有线程共享的一块最大的内存区域。堆的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 JDK1.7后默认开启逃逸分析,如果某些方法中的对象引用没有被返回或未被外面使用(即未逃逸),那么对象可以直接在栈上分配内存。 堆也称为GC堆,是垃圾收集器管理的主要区域。收集器基本都采用分代垃圾收集算法,因此堆可以细分为:新生代、老年代、永久代,JDK8后永久代被元空间取代。新生代还能划分为Eden、Survivor。详见内存分配与回收原则。 程序运行中堆最容易出现 OutOfMemoryError 错误,例如: java.lang.OutOfMemoryError: GC Overhead Limit Exceeded:当JVM花费过多的时间进行GC,却只能回收很少的堆空间时。 java.lang.OutOfMemoryError: Java heap space:堆内存不足以存放新创建的对象。(受制于配置的最大堆内存 -Xmx 和物理内存大小。) 方法区 方法区是JVM运行时线程共享的一块逻辑区域(抽象概念),在不同的虚拟机上,方法区的实现(如永久代、元空间)是不同的。当JVM要使用一个类时,它会读取并解析Class文件获取相关信息(类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等),再将信息存入到方法区。 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) ? 永久代受制于JVM设置的固定大小 -XX:MaxPermSize ,启动后无法调整,加载大量类时容易出现 java.lang.OutOfMemoryError: PermGen space 错误;而元空间使用本地内存,加载多少个类的元数据只受制于本机实际可用内存,溢出的概率更小。(元空间溢出时会出现 java.lang.OutOfMemoryError: MetaSpace 错误) -XX:MaxMetaspaceSize:设置最大元空间大小,默认值为unlimited。 -XX:MetaspaceSize:设置元空间的初始大小,如果未指定,则根据运行时的应用程序需求动态调整。 永久代与堆共享垃圾回收器,增加了GC的复杂度,且回收效率低。 JDK8以前,HotSpot通过永久代存储类的元数据,而JRockit中没有类似的概念,因此在两者合并后,就没有必要额外设置一个永久代了。 运行时常量池 Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量和符号引用的常量池表(Constant Pool Table)。常量池表会在类加载后存放到方法区或元空间的运行时常量池中,内存受制于方法区或元空间。 字面量:程序中显式写出的值,如数字、字符、字符串等。 符号引用:对类、字段、方法、接口方法等的引用。解析阶段就是JVM把常量池中的符号引用替换成直接引用的过程。 字符串常量池 字符串常量池是JVM为了提

目录 概述 语法和重要概念 数据类型 集合 异常 一、概述 Java语言的特点 面向对象:封装、继承、多态。 封装(Encapsulation):将数据和操作数据的方法包装在一起,隐藏内部细节,只能通过对外提供的接口,对封装在内部的属性和方法进行访问和操作。 继承(Inheritance):子类复用和扩展父类的属性和方法,实现层次结构。 多态(Polymorphism):调用相同方法做出不同行为。两种实现方式:通过继承(子类方法重写)、通过接口。 平台无关性:JVM实现“Write Once, Run Anywhere.”。 可靠性:异常处理、自动内存管理机制。 安全性:如访问权限修饰符、限制程序直接访问操作系统资源。 编译与解释共存 .java 文件编译 为 *字节码*(JVM能理解的代码,即 .class 文件),字节码解释为 *机器码* 。由于字节码只面向JVM,因此程序无须重新编译便可在不同操作系统上运行。 JIT(Just in Time Compilation)编译器:运行时编译。当JIT编译器完成第一次编译后,会将字节码对应的机器码保存下来,下次直接使用。 AOT(Ahead of Time Compilation):在程序被执行前就将其编译成机器码,可以提高程序的启动速度,避免预热时间长,但无法支持反射、动态代理、动态加载、JNI(Java Native Interface)等。 二、语法和重要概念 重载和重写 重载(Overload):同一类中方法名相同,参数列表不同。发生在编译期。 重写(Override):子类重写父类方法,子类方法的返回值类型和异常比父类方法更小或相等;发生在运行期。 浅拷贝和深拷贝 浅拷贝:在堆上创建一个新对象,如果原对象内部属性有引用类型,浅拷贝会直接复制内部对象的引用地址,也就是说和原对象共用同一个内部对象。 深拷贝:会完全复制整个对象,包括其所包含的内部对象。 序列化和反序列化 位于 TCP/IP 协议中的应用层:将应用层的用户数据进行处理转换为二进制流。 序列化:将数据结构或对象转换成二进制字节流、JSON 、XML 等。 反序列化:将序列化过程中生成的数据转换为原始数据结构或对象的过程。 应用场景:网络传输、存储到文件、存储到缓存数据库、存储到内存。 JDK自带的序列化方式:实现 java.io.Serializable 接口。 serialVersionUID 用于标识类的序列化版本,没有被序列化。 static 修饰的变量因为不属于任何对象,所以不会被序列化。 transient 修饰的变量不会被序列化,反序列化后会被置成类型默认值。 缺点:不支持跨语言调用、性能差、安全问题。 其它序列化方式:Kryo、Protobuf、ProtoStuff、Hessian。 强引用/软引用/弱引用/虚引用 | 类型 | 特点 | 典型用途 | |-----------------|--------------------------------------------------------------------------------------------------|---------------------------------| | 强引用 | 普通的对象引用,垃圾回收器不会回收。 | 常规对象使用。 | | 软引用 | 内存不足时回收对象; 可以和引用队列联合使用。 | 缓存数据(如图片缓存)。 | | 弱引用 | 不论内存是否充足,GC时都会回收; 可以和引用队列联合使用。 | 缓存、引用池中的对象引用。 | | 虚引用 | 不能访问对象,用于跟踪对象回收; 必须和引用队列联合使用。 | 监控对象回收、清理资源。 | 反射 / 注解 代码块执行顺序 顺序:静态代码块(只执行一次)->普通代码块->构造函数->普通代码块->构造函数 值传递 Java只有值传递,没有引用传递,方法接收的是实参值的拷贝(可以是实参的地址),会创建副本。 三、数据类型 包装类型 包装类型与基本类型的区别 用途:除了定义一些常量和局部变量之外,方法参数、对象属性中常用包装类型。包装类型可用于泛型,而基本类型不行。 存储方式:基本类型的局部变量存放在栈的局部变量表中,成员变量(未被 static 修饰)存放在堆中。包装类型属于对象类型,因此存在堆中。 占用空间:基本类型占用的空间往往更小。 默认值:基本类型有默认值,而包装类型为 null。 比较方式:因为包装类型是对象,== 比较的是内存地址,equals() 比较的是值。 阿里Java开发手册: 【强制】定义 DO/DTO/VO 等 POJO 类时,不要设定默认值。 【强制】所有的 POJO 类属性必须使用包装数据类型。 【强制】RPC 方法的返回值和参数必须使用包装数据类型。 【推荐】所有的局部变量使用基本数据类型。 包装类型的缓存机