语言细节、并发原语、类加载与 GC——两块拼起来才是完整的 Java 运行时图景。

一、语言与对象

Java 内部类

内部类是定义在另一个类里面的类,与之相对应,包含内部类的类被称为

外部类,

内部类的作用有:(1)内部类提供了更好的封装,可以把内部类隐藏在外

部类之内,不允许同一包中的其他类访问,(2)内部类的方法可以直接访问外

部类的所有数据,包括私有的数据,(3)内部类的种类:成员内部类、静态内

部类、方法内部类、匿名内部类

Java 关键字 final 和 static 是怎么使用的。

final:

1、final 变量即为常量,只能赋值一次。

2、final 方法不能被子类重写。

3、final 类不能被继承。

static:

1、static 变量:对于静态变量在内存中只有一个拷贝(节省内存),JVM 只为静态分配一次

内存,

在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过

对象来访问(但是这是不推荐的)。

Java static

代码块:

static 代码块是类加载时,初始化自动执行的。

static 方法

static 方法可以直接通过类名调用,任何的实例也都可以调用,因此 static 方法中不能用 this

和 super 关键字,

不能直接访问所属类的实例变量和实例方法(就是不带 static 的成员变量和成员成员方法),

只能访问所属类的静态成员变量和成员方法

Java String、StringBuffer、StringBuilder 区别

1、三者在执行速度上:StringBuilder > StringBuffer > String (由于 String 是常量,不可改

变,拼接时会重新创建新的对象)。

2、StringBuffer 是线程安全的,StringBuilder 是线程不安全的。(由于 StringBuffer 有缓冲区)

Java 反射机制

Java 的反射机制是在运行状态中,对于任意一个类,都能够获取到这个

类的所有属性和方法,对于任意一个对象,能够调用它的任意一个方法和属

性, 包括私有的方法和属性,这种动态地获取信息以及动态的调用对象的方

法的功能就称之为 Java 的反射机制。

从对象出发,通过反射(.class 类)可以获取到类的完整信息,(类名、

class 类型、所在包、具有的所有方法 Method[]类型、某个方法的完整信息,

包括修饰符、返回值类型、异常、参数类型、所有属性 Field[ ]、某个属性的完

整信息,构造器 Constructors,调用类的属性或方法。

Java Cglib

CGLIB 用子类拦截方法:Enhancer 生成未实现接口也能代理的类。Spring AOP 在目标无接口时走 CGLIB。final 类/方法无法增强。Objenesis 可绕过构造器。

对比 JDK 动态代理:后者只对接口。CGLIB 调用 super 时要注意两次调用(equals/hashCode)。Java 9+ 模块对非法访问更严,Spring 已转向更高版本的代理实现。

Android 上 ART 对运行时代理不友好,一般不用 CGLIB。排查 AOP 失效先看是否 final、是否自调用绕过代理。生成类会占 Metaspace,注意泄漏 Enhancer 缓存。

二、并发

可重入锁(递归锁)

可重入锁允许同一线程多次获得同一把锁而不死锁。Java 里 synchronized 和 ReentrantLock 都是可重入的:内部记持有线程和重入次数,释放时次数减到零才真正放开。递归调用、同一个类里多个同步方法互相调用,都依赖这个特性。不可重入锁在这种场景会把自己卡住。

可重入不等于可以随便嵌套。嵌套过深会让锁持有时间变长,放大竞争。公平锁能减少饥饿,但吞吐通常更差。写业务时要分清锁保护的是哪块状态,避免一把大锁包住无关代码。排查死锁用 jstack 看等待图。能用并发集合或分段锁就不要把整张表锁住。重入次数异常升高,往往说明调用路径有意外递归。

Java 线程中 sleep 和 wait 的区别

(1)这两个方法来自不同的类,sleep 是来自 Thread,wait 是来自 Object;

(2)sleep 方法没有释放锁,而 wait 方法释放了锁。

(3)wait,notify,notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何

地方使用。

Java JUC:Semaphore控制某个方法允许并发访问线程的个数

在Java 中常使用Semaphore(信号量)来进行并发编程,Semaphore 控

制的是线程并发的数量,实例化一个Semaphore 对象,如Semaphore

semaphore= newSemaphore(5,true) ,其创建了对象 semaphore,并初始化了5 个信号量,

即最多允许5 个线程并发访问,在执行的任务中,调用 semaphore 的acquire

() 方法请求一个信号量,这时信号量个数就减 1,(一旦没有可使用的信号

量,再次请求就会阻塞),来执行任务,执行完任务,调用 semaphore 的

release()方法释放一个信号量此时信号量的个数就会加 1 。

Netty

Netty 是异步事件驱动的网络框架:Boss 接连接,Worker 跑 NIO 事件。ChannelPipeline 串 Handler,入站出站方向相反。ByteBuf 池化减少分配。编解码用 LengthField 或自定义帧。

适合高并发 TCP 网关、MQTT Broker、RPC。零拷贝与堆外内存要配对 leak 检测。心跳 IdleStateHandler。粘包拆包必须在解码器解决,不能假设一次 read 一帧。

线程模型不要在 Handler 里阻塞。业务丢到业务线程池。Android 上较少直接用 Netty,更多在服务端。调优看水位线 WRITE_BUFFER_WATER_MARK 与连接数。

三、JVM

JVM 垃圾回收器及其算法

JVM 垃圾回收先分算法再分回收器。标记-清除、复制清理、分代收集是基础;Serial/Parallel 偏吞吐,CMS 用并发标记降低 STW 但有碎片,G1 按 Region 做混合回收,ZGC 用着色指针追求把停顿压到毫秒级。

选垃算法时要看对象生命周期:短命对象适合复制清理,老年代适合标记整理。分代假设大多数对象朝生夕死,这也是 Eden/Survivor 存在的原因。

读参数前先把各收集器的 STW 点画清楚:Initial Mark、Remark、Cleanup 各占多久。面试常问 CMS 浮动垃圾、G1 Mixed GC 与 ZGC 的 Relocation Set 如何选取。

Java 双亲委派机制

双亲委派让类加载先请示父加载器,避免核心类被替换。启动类加载器、平台/扩展、应用加载器形成链。相同类名由不同加载器加载则类型不相等。

需要打破的场景:Tomcat 隔离 WebApp、JDBC SPI 由启动器加载接口却要找实现、热部署自定义加载器。实现时常重写 loadClass 先找自己再委派。

线程上下文加载器是 SPI 的补丁。排查 ClassNotFound 与 LinkageError 要打印加载器树。Android 的 PathClassLoader/DexClassLoader 类似但委托规则不同。不要随意 setContextClassLoader。

JVM 调优

JVM 调优从业务指标出发:延迟、吞吐量、Full GC 频次与 CPU 。先用 GC 日志、jstat、async-profiler 看清是分配过快、老年代过大还是 STW 过长,再动 -Xms/-Xmx、新生代比例和回收器参数。

G1 下关注 MaxGCPauseMillis、混合回收与 Humongous 对象;ZGC/Shenandoah 更适合大堆低停顿。Metaspace 泄漏要查动态类加载,直接加堆往往只是掩盖问题。

避免一次改一堆参数。每次只改一类因子并对比压测。上线前用同等负载的 GC log 做回湮,把平均停顿与 P99 写进容量标准。

四、工程

Checkstyle

Checkstyle 按规则检查 Java 源码风格:命名、import 顺序、括号位置、行宽和 javadoc。它不替代编译器,只是把团队约定变成可自动执行的门禁,常挂在 Maven / Gradle 和 CI 上。

落地时先选一份接近团队现状的配置(Google 或 Sun),再逐项打开,而不是一次开到最严导致全红。抑制注解要写原因,避免大面积 ignore。和 Spotless / Palantir 格式化工具搭配时,风格类问题交给格式化器,Checkstyle 专注语义约定。规则文件应进版本库,保证本地与流水线一致。

Java-JNI

JNIEnv 和 JavaVM

1.JavaVm

JavaVM 是虚拟机在 JNI 层的代表,一个进程只有一个 JavaVM,所有的线程

共用一个 JavaVM。

2.JNIEnv

JNIEnv 表示 Java 调用 native 语言的环境,是一个封装了几乎全部 JNI 方法

的指针。

JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的 JNIEnv 彼此独立。

native 环境中创建的线程,如果需要访问 JNI,必须要调用

AttachCurrentThread 关联,并使用 DetachCurrentThread 解除链接。