没有做防注入的网站,wordpress 总站模板,做网站需要公司吗,软件开发模型螺旋模型String——字符串#xff0c;使用进行基本表示。 注意#xff1a;字面量方式会检查字符串常量池#xff0c;若存在则直接返回引用#xff1b;new方式则是强制在堆内存当中开辟新空间#xff08;JDK7之后常量池在堆当中#xff09;#xff0c;即使内容相同也…String——字符串使用进行基本表示。注意字面量方式会检查字符串常量池若存在则直接返回引用new方式则是强制在堆内存当中开辟新空间JDK7之后常量池在堆当中即使内容相同也会生成新对象除非调用String.internString被声明为final类不可被继承public final class String意味着String不能有子类设计目的保障不可变性防止子类修改字符串内部状态确保字符串内容一旦创建就不可修改安全性String广泛用于网络参数、数据库连接、类加载器等关键场景不可继承防止恶意篡改缓存哈希值内容不变String可以安全缓存hashCode非常适合作为hashmap的keyString实现了关键接口Serializable表示字符串支持序列化可以通过网络传输/持久化到文件/数据库ComparableString:String定义了自然排序规则。比较方式按照字典序逐个字符进行比较用于String之间排序操作。内部存储结构改变JDK8 VS JDK9JDK8之前内部定义为private final char[] value;特点使用UTF-16编码每个字符固定占用2字节即使存储纯英文ASCII也会浪费50%空间JDK9之后引入Compact Strings内部定义为private final byte[] value;并新增private final byte coder字段特点根据字符串内容进行动态编码若仅包含Latin-1字符英文、西欧符号、数字等coder标记为LATIN1每个字符占用1个字节.包含非Latin-1字符例如中文等coder标记为UTF16每个字符占用2个字节。优势大幅减少内存占用纯英文减少50%降低GC压力并提升缓存命中率String.intern()作用String.intern()是一个本地方法主要作用将字符串对象放入/取自字符串常量池当中。具体逻辑检查字符串常量池当中是否存在JVM会检查字符串常量池当中是否已经存在一个与当前字符串内容相等通过 equal()的字符串若存在返回池中已经有的字符串引用不存在当前字符串添加到字符串常量池当中返回这个新加入池中的引用核心目的确保在堆内存当中内容相同的字符串只能保留一份实例从而节省内存空间。为什么JDK9改变了String结构官方文档JEP 254: Compact Strings为什么JDK9要改用byte[]早期Java应用当中大量字符串是纯ASCII码日志、json、url等。使用char[]并强制每个字符占用两个字节及其浪费。JDK9的Compact Strings通过增加一个判断位coder让大多数字符内存占用减少一半显著提升整体性能。String基本特性String代表不可变的字符序列。不可变性当对字符串进行重新赋值时JVM会在内存通常是字符串常量池当中查找/创建一个新的字符串对象并将变量的引用指向这个新的对象。原有的String对象不受影响且原对象内部value数组未被修改。当对现有字符串进行连接操作时系统创建一个新的String对象存储连接后结果原字符串依然保持原样。当调用String的replace() 、substring()等方法时这些方法不会修改调用者本身而是返回一个全新的String对象包含修改后的内容通过字面量的方式区别于new给字符串赋值此时字符串值声明在字符串常量池当中若常量池当中已经存在相同内容字符串则直接返回引用否则创建新的实例放入字符串常量池当中EXMAPLE对字符串重新赋值时需要重新指定内存区域不能修改原有valueTest public void test1() { // 字面量定义abc 存储在字符串常量池中 String s1 abc; String s2 abc; // s2 指向常量池中同一个 abc 对象 // 重新赋值s1 现在指向常量池中新的 hello 对象 // 原来的 abc 对象依然存在且 s2 仍然指向它 s1 hello; //本质上是将局部变量表当中槽位更新为指向hello的引用 // 判断地址s1 指向 hello, s2 指向 abc地址不同 System.out.println(s1 s2); // 输出false System.out.println(s1); // 输出hello System.out.println(s2); // 输出abc (证明原对象未变) }对现有字符串进行连接操作时需要生成新的对象不能修改原有valueTest public void test2() { String s1 abc; String s2 abc; // 连接操作底层相当于 s2 new StringBuilder(s2).append(def).toString(); // 结果生成了一个新的 String 对象 abcdefs2 指向新对象 s2 def; System.out.println(s2); // 输出abcdef (新对象) System.out.println(s1); // 输出abc (原对象 s1 未受影响s2 原来的引用也没法改它) // 验证s1 和 s2 不再指向同一个对象 System.out.println(s1 s2); // 输出false }注意推荐在循环当中使用 StringBuilder进行字符串拼接避免产生大量临时对象。这里使用清洗体现了String不可变性——结果是一个新对象调用String的replace()方法修改指定限定字符时返回新对象原对象不变Test public void test3() { String s1 abc; // replace 方法不会修改 s1 内部的字符数组 // 而是创建一个新的 String 对象 mbc 并返回给 s2 String s2 s1.replace(a, m); System.out.println(s1); // 输出abc (原对象纹丝不动) System.out.println(s2); // 输出mbc (新对象) System.out.println(s1 s2); // 输出false }常见面试题String不可变性数组可变性java的值传递机制public class StringExer { // 成员变量 String str new String(good); char[] ch {t, e, s, t}; public void change(String str, char[] ch) { // 1. String 参数str 是传入引用的副本。 // 这里让局部变量 str 指向常量池中的新对象 test ok。 // 这不会影响外部的 ex.str 指向。 str test ok; // 2. 数组参数ch 是传入引用的副本但指向堆中同一个数组对象。 // 通过引用副本修改数组内部元素外部可见。 ch[0] b; } public static void main(String[] args) { StringExer ex new StringExer(); ex.change(ex.str, ex.ch); // 结果分析 // ex.str 依然指向堆中 new String(good) 创建的对象内容为 good System.out.println(ex.str); // 输出good // ex.ch 指向的数组内容被修改了 System.out.println(ex.ch); // 输出best } }为什么ex.str没变原因1值传递机制Java当中对象参数传递是引用的副本。方法内str和外部的ex.str实际上是两个独立变量知识初始值指向的是同一个对象。原因2String不可变性即使我们在方法当中尝试修改字符串strtest ok实际上也只是让方法内局部变量str指向了字符串常量池当中新对象test ok.String对象是不可变的我们无法通过任何手段修改原来的good对象内部任何内容。为什么ex.ch变了原因数组可变性char[]是可变对象。虽然传递的也是引用的副本但是这个副本和引用指向的都是堆内存当中同一个数组对象操作ch【0】b通过引用找到可堆上数组并直接修改了数组内部数据。结论修改是物理层面的对外部完全可见。String底层——StringTable字符串常量池核心原则字符串常量池StringPool当中不会存储内容重复字符串。当尝试放入一个已经存在的字符串时会直接返回已有对象引用。底层数据结构与演变StringTable是HotSpot VM内部维护的一个全局hash table哈希表用于实现字符串常量池。数据结构本质上是个数组数组每个元素Bucket指向一个链表Entry发生哈希冲突时新元素添加到链表当中注意⚠与Java集合框架当中HashMap不同的是HotSpot内部的StringTable在链表过长时不会自动转为红黑树。因此若桶Bucket数量过大链表会变得很长导致查找效率从O(1)退化为O(n)性能影响若StringTable尺寸过小哈希冲突严重链表过长。后果大幅降低String.intern方法执行效率还会显著拖慢类加载过程因为类文件当中字符串字面量需要在加载时解析并放入池中进而影响应用启动速度。不同版本实现差异JDK 版本默认大小 (Buckets)是否可配置 (-XX:StringTableSize)说明JDK 61009否 (或无效)固定大小。由于尺寸太小在大型应用中极易发生哈希冲突导致性能瓶颈。无法通过参数有效调整。JDK 7 (u40 之前)1009是延续了 JDK 6 的默认小尺寸但开始允许通过参数调整。JDK 7 (u40 及之后)60013是重大优化。默认大小提升至 60013极大减少了哈希冲突概率。JDK 8 及以上60013是保持 60013 的默认值。这是目前生产环境的推荐基准。参数配置参数名称-XX:StringTableSizeN配置规则质数原则为了获取最佳哈希分布N最好是一个质数。若传入非质数HotSpot通常会自动寻找并调整为最接近的质数大小建议对于绝大多数应用而言默认的60013已经够用若应用包含大量字符串字面量生成的代码、巨大的配置类文件等或者频繁调用String.intern建议调大进一步减少冲突提升启动性能。最小值虽然没有强制最小值为1009这类的限制但是设置为过小的值例如小于1009会导致严重的性能退化应该避免。字符串拼接内存分配EXString s1 javaEE; String s2 hadoop; String s3 javaEEhadoop; String s4 javaEE hadoop String s5 s1 hadoop; String s6 javaEE s2; String s7 s1 s2;简单结论只要拼接操作当中存在变量注意不包含常量最终结果一定是在堆内存当中创建一个新对象。编译器优化运行期计算纯字面量拼接优化String s4 javaEE hadoop;过程Javac(java编译器编译阶段直接进行常量折叠Constant Folding结果生成的字节码中这一行直接变为String s4javaEEhadoop内存只在字符串常量池当中查找或者创建一个对象。不会在堆内存创建多余的新对象包含变量的拼接运行期计算String s5 s1 hadoop; String s6 javaEE s2; String s7 s1 s2;过程因为 s1和s2是变量它们的值在编译期未知必须在运行期才能确定。结果JVM必须分配新的内存空间存储拼接后的新字符串序列这个新对象位于堆内存当中新对象默认不在字符串常量池当中除非手动调用intern方法内存会在堆中new一个新的String对象。特殊情况final编译器常量若变量声明为final情况发生变化final String s1javaEE; String s5s1hadoop;分析因为s1是final修饰的它在编译期就是一个常量。结果编译器会将其视为纯粹字面量进行拼接进行常量折叠结论此时不会在堆当中新建对象而是直接使用常量池当中对象。注意若final String s1new StringjavaEE,则s1不是编译常量无法折叠底层演变JDK8以及之前StringBuilderjdk8当中编译器会将s1hadoop自动转换为类似以下代码// 编译器实际生成的伪代码 String s5 new StringBuilder().append(s1).append(hadoop).toString();StringBuilder.toString()内部会执行newString(value)涉及到new操作会在堆中创建StringBuilder对象和最终的String对象JDK9以及之后invokedynamic从JDK9开始为了优化性能编译器不再机械的使用StringBuilder而是使用invokedynamic指令。运行机制JVM在运行时动态决定最高效的拼接策略结果底层计数变了但是结果不变——依然是在堆上生成一个新的String对象EXnew String(a)new String(b)会创建几个对象public class StringNewTest { public static void main(String[] args) { String str new String(a) new String(b); } }详细分析常量池对象aJVM检查常量池当中是否存在a,不存在则创建堆对象new String(a)堆内开辟空间创建String对象内容复制a常量池对象b :JVM检查常量池当中是否存在b,不存在则创建堆对象 new String(b):堆内开辟空间创建String对象内容复制b堆对象StringBuilderJDK8以及之前创建:使用StringBuilder进行拼接堆对象ab:最终创建拼接后的堆对象abStringTable字符串常量池具体实现的垃圾回收StringTable是什么其本质是一个Hashtablec实现简化版源码// HotSpot 源码中的 StringEntry 结构 class StringEntry : public HashtableEntryoop, mtSymbol { // 没有 Key-Value 分离 unsigned int _hash; // 字符串的 hashCode() oop _literal; // 直接指向堆中 String 对象的弱引用 StringEntry* _next; // 链表下一个 Entry };本身不存储字符内容字符内容存储在堆内String对象当中核心概念StringTable并不是堆中一个独立分区而是HotSpot虚拟机维护的一个全局哈希表。JDK6以及之前StringTable位于永久代难以回收容易导致OOMJDK7以及之后StringTabl移至Java堆当中。StringTable当中条目entry所引用的对象和不同堆对象一样受到GC管理回收流程触发时机StringTable清理不会单独发生而是伴随Minor GC或者Full GC标记-清除当GC开始时JVM会遍历所有StringTable检查表中每个Entry所指向的String对象是否在GC Roots可达即是否被栈、静态变量等引用若某个String对象在堆中已经不可达GC不仅回收String对象占用的内存还同步移除StringTable中对应的EntryEXAMPLE代码中硬编码的字面量String s hello; s null;来源编译期确定写入.class文件常量池引用链Metaspace (Class - ConstantPool) -- Heap (String Object) -- StringTable。虽然s不再引用我们这个字面量但是常量池当中依然记载了这个字面量GC结果记录字面量的String对象存活存在常量池强引用String Table对应的Entry:存活结论对应的类不卸载hello永远在堆当中不会被GC回收。运行时动态生成并intern的字符串String s new String(a) new String(b); // 生成 ab s.intern(); s null;来源运行时计算生成valuOf、拼接等原本并不在常量池引用链StringTable→HeapString Object。并不存在类常量池的强引用。GC结果String对象被回收若业务代码也不再引用StringTable Entry:被移除G1当中字符串去重操作背景动机统计项数据说明堆存活数据中 String 占比~25%许多 Java 应用的内存瓶颈String 对象中重复比例~13.5% (约一半)str1.equals(str2) true 但 str1 ! str2String 平均长度~45 字符去重收益显著核心问题堆上存储的大量内容相同但是不同对象的String造成内存浪费。StringTableString Deduplication 关键区别特性StringTableString Deduplication本质字符串常量池G1 的堆内存优化功能去重时机类加载时 / intern() 调用时GC 过程中并发进行去重范围字面量 手动 intern() 的字符串所有满足条件的 String 对象存储位置堆JDK7堆G1 Region 中引用类型弱引用强引用去重后共享是否自动字面量自动其他需手动 intern()全自动开启参数后底层数组不关心Java 8: char[] / Java 9: byte[]StringTable常量池 2• 目的保证字面量唯一性 3• 生命周期类卸载前一直存在 4• 引用弱引用 5• 范围只有字面量和 intern() 的字符串 6 7Deduplication Table去重表 8• 目的减少堆内存浪费 9• 生命周期GC 周期内有效 10• 引用强引用去重后共享 11• 范围所有满足年龄阈值的 String 对象Duduplication实现原理Step1GC扫描阶段G1 GC遍历堆当中所有存活对象检查每个对象是否是String类型检查是否满足去重条件年龄≥stringDeduplicationAgeThreshold默认为3未被处理过符合条件的String引用放入去重队列并发处理阶段后台线程去重线程从队列当中取出String引用计算String的hash获取内部数组引用byte[]/char[])查询去重表存在相同内容的String→执行去重不存在→插入去重表去重执行假设String A和String B内容相同去重前String A ────▶ byte[] [0x111] hello String B ────▶ byte[] [0x222] hello ← 重复内存去重后String A ────┐ ├───────▶ byte[] [0x111] hello String B ────┘命令行参数参数类型默认值说明-XX:UseG1GCboolean否必须开启仅支持 G1ZGC 后续也支持-XX:UseStringDeduplicationbooleanFALSE开启 String 去重功能-XX:PrintStringDeduplicationStatisticstruebooleanFALSE打印去重统计信息-XX:StringDeduplicationAgeThreshold3uintx3String 对象经历多少次 GC 后成为候选-XX:StringDeduplicationHashSeed54321uintx随机哈希种子防哈希碰撞攻击统计参数解读开启PrintStringDeduplicationStatistics后输出示例String Deduplication Statistics: Total Queued: 1000000 ← 进入去重队列的 String 总数 Total Eliminated: 500000 ← 成功去重的 String 数量 Total Failed: 0 ← 去重失败数量 Deduplication Ratio: 50.0% ← 去重比例 Memory Saved: 10.5 MB ← 节省的内存