JVM逃逸分析:对象并非必然在堆中分配
曾有一道面试题:“Java中的对象都是在堆中分配吗?”——答案是否定的,这涉及到JVM的逃逸分析技术。 什么是逃逸分析?逃逸分析是JVM在编译期进行的一种优化技术,用于分析对象的作用域。若确定一个对象仅在当前方法或线程内被引用,未“逃逸”到外部,则JVM可能将其分配在栈上,甚至直接进行标量替换,从而提升性能。 相关JVM参数: 开启:-XX:+DoEscapeAnalysis(默认启用) 关闭:-XX:-DoEscapeAnalysis 打印分析结果:-XX:+PrintEscapeAnalysis 对象的逃逸状态全局逃逸:对象被其他线程或方法引用,或作为静态变量、返回值。 参数逃逸:对象作为参数传递,但未进一步逃逸。 无逃逸:对象生命周期完全局限于当前方法。 基于无逃逸的优化 锁消除若对象仅被当前线程访问,JVM会移除其同步锁。例如StringBuffer的同步操作在无竞争时可被消除。 标量替换将对象拆解为其成员变量(标量),直接在栈或寄存器中分配,避免创建完整对象。 栈上分配将原本应在堆中分配的对象改为在栈帧中分配,随方法结束自动销毁,减轻GC压力。 编码启示在开发...
Java宏变量与编译期常量替换机制解析
观察以下程序及其输出: textpublic static void main(String[] args) {String hw = “hello world”;String hello = “hello”;final String finalWorld2 = “hello”;final String finalWorld3 = hello;final String finalWorld4 = “he” + “llo”; String hw1 = hello + " world"; String hw2 = finalWorld2 + " world"; String hw3 = finalWorld3 + " world"; String hw4 = finalWorld4 + " world"; System.out.println(hw == hw1); // false System.out.println(hw == hw2); // t...
String不可变性的本质与极限突破
String类被设计为不可变,其源码关键字段如下: textpublic final class String {private final char value[];private int hash;}类为final,且内部字符数组为private final,因此常规操作无法修改其内容。 常规情况下的不可变性以下代码仅改变了引用指向,而非原字符串内容: textString str = “Python”;str = “Java”; // 指向新对象str = str.substring(1); // 创建新对象 “ava”https://img/18-9-12-688492.jpg substring、replace等方法均返回新String对象,原对象保持不变。 通过反射突破限制利用反射可修改final数组内的元素,从而“改变”字符串: textString str = “Hello Python”;Field field = String.class.getDeclaredField...
三种获取Java类名的方法对比与适用场景
Java中获取类名的主要方式有三种,分别返回不同格式的类名信息: getName():返回JVM内部使用的类名表示形式。 getCanonicalName():返回更易理解的规范类名。 getSimpleName():返回不包含包名的简称。 通过以下示例可清晰辨别三者差异: textpublic class TestClass {public static void main(String[] args) {// 外部普通类System.out.println(“方法名 类名”);System.out.println(“getName “ + TestClass.class.getName());System.out.println(“getCanonicalName “ + TestClass.class.getCanonicalName());System.out.println(“getSimpleName “ + TestClass.class.getSimpleName());System.out.println(...
五种典型场景下空指针异常的预防策略
空指针异常(NullPointerException)是Java开发中最常见的运行时异常之一,其根源在于试图访问或操作值为null的对象引用。尽管普遍,但通过良好的编码习惯可有效减少其发生。 空指针异常的本质当一个变量值为null时,它表示一个未指向任何内存空间的对象引用。若此时调用该变量的方法或访问其属性,便会触发空指针异常。例如: textObject object = null;String string = object.toString(); // 抛出 NullPointerExceptionhttp://qianniu.javastack.cn/18-12-12/46377586.jpg 从异常类结构看,空指针异常继承自RuntimeException,属于非受检异常,通常只有在程序运行时才会暴露,并可能导致流程中断。 五种常见场景与规避方案场景一:字符串比较时将常量置于前方不推荐写法: textif(status.equals(SUCCESS)){}推荐写法: textif(SUCCESS.equals(status)){}...
利用固定种子随机数生成指定字符串的原理剖析
下面展示一段基于固定种子的随机数生成程序: textpublic static void main(String[] args) {System.out.println(randomString(-229985452) + “ “ + randomString(-147909649));} public static String randomString(int seed) {Random ran = new Random(seed);StringBuilder sb = new StringBuilder();while (true) {int k = ran.nextInt(27);if (k == 0) {break;}sb.append((char) (‘`’ + k));}return sb.toString();}该程序每次运行都会输出:hello world,原因何在? 在Java中,Random构造函数的seed参数用于设定随机数生成的初始种子。相同的种子会产生完全相同的随机数序列,因此无论执行多少次,生...
剖析伪共享现象及其在Java中的应对策略
伪共享概念CPU缓存以缓存行(通常64字节)为单位存储数据。当多个线程修改位于同一缓存行的不同变量时,会无意中导致缓存行无效,引发频繁的缓存同步,这种现象称为伪共享(FalseSharing)。 缓存体系结构现代CPU通常具备三级缓存(L1、L2、L3),速度逐级递减,容量逐级增大。L1、L2为核内私有,L3为多核共享。数据访问时依次查找各级缓存,未命中则访问主内存。 https://img/18-5-31-91078691.jpg MESI缓存一致性协议MESI定义了缓存行的四种状态: M(Modified):已修改,与主存不一致,仅存在于当前缓存。 E(Exclusive):独占,与主存一致,仅存在于当前缓存。 S(Shared):共享,与主存一致,可能存在于多个缓存。 I(Invalid):无效。 当某核修改共享数据时,其他核中对应缓存行状态将变为I,需重新从主存加载。 https://img/18-5-31-66429246.jpg Java中的传统解决方案通过填充无用字段,使单个对象独占一个缓存行,避免多个变量共享同一缓存行。 textpublic fi...
字符串拼接:+ 运算符与StringBuilder的适用边界
在Java开发中,我们常被告知字符串拼接应使用StringBuilder或StringBuffer,而非 + 运算符。然而,这一准则并非绝对,需视场景而定。 不适用 + 的场景当字符串拼接分散在多个表达式时,每次 + 操作都会隐式创建新的StringBuilder对象,效率低下。 textprivate void test1() {String www = “www.”;String str = www;str += “javastack.”;str += “com”;}对应字节码中会出现两次 NEW java/lang/StringBuilder,在循环中频繁拼接时将严重影响性能。 适用 + 的场景当所有拼接在同一表达式内完成,且均为字面量时,编译器会进行优化,直接合并为一个完整字符串。 textprivate static void test2() {String str = “www.” + “javastack.” + “com”;}字节码中仅有一条 LDC “www.javastack.com“ 指令...
区分hashCode与identityHashCode:重写与否的关键差异
identityHashCode 概述System.identityHashCode(Object x) 是一个本地方法,其作用是返回对象的原始哈希码,无论该对象的类是否重写了hashCode()方法。 对比示例textpublic static void main(String[] args) {String str1 = new String(“abc”);String str2 = new String(“abc”);System.out.println(“str1 hashCode: “ + str1.hashCode()); // 96354System.out.println(“str2 hashCode: “ + str2.hashCode()); // 96354System.out.println(“str1 identityHashCode: “ + System.identityHashCode(str1)); // 1173230247System.out.println(“s...
实现自定义类加载器加载外部类文件
当需要加载不在classpath下的类文件时,可通过自定义ClassLoader实现。以下示例演示如何加载位于 c:/test/com/test/jdk/Key.class的类。 目标类: textpackage com.test.jdk;public class Key {private String key = “111111”;}自定义类加载器: textimport org.apache.commons.io.IOUtils;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream; public class LocalClassLoader extends ClassLoader {private String path = “c:/test/“;@Overrideprotected Class findClass(String name) throws ClassNot...
