湘潭哪里做网站,wordpress 添加php文件,中国建设银行官网站黄金部王毅,wordpress悬浮客户1、总体路线 2、程序计数器
Program Counter Register 程序计数器#xff08;寄存器#xff09; 作用#xff1a;是记录下一条 jvm 指令的执行地址行号。 特点#xff1a;
是线程私有的不会存在内存溢出
解释器会解释指令为机器码交给 cpu 执行#xff0c;程序计数器会…1、总体路线 2、程序计数器
Program Counter Register 程序计数器寄存器 作用是记录下一条 jvm 指令的执行地址行号。 特点
是线程私有的不会存在内存溢出
解释器会解释指令为机器码交给 cpu 执行程序计数器会记录下一条指令的地址行号这样下一次解释器会从程序计数器拿到指令然后进行解释执行。 多线程的环境下如果两个线程发生了上下文切换那么程序计数器会记录线程下一行指令的地址行号以便于接着往下执行。
3、栈
定义
每个线程运行需要的内存空间称为虚拟机栈 每个栈由多个栈帧Frame组成对应着每次调用方法时所占用的内存 每个线程只能有一个活动栈帧对应着当前正在执行的方法
问题辨析
垃圾回收是否涉及栈内存
不会。栈内存是方法调用产生的方法调用结束后会弹出栈。
栈内存分配越大越好吗
不是。因为物理内存是一定的栈内存越大可以支持更多的递归调用但是可执行的线程数就会越少。
方法内的局部变量是否线程安全
如果方法内部的变量没有逃离方法的作用范围它是线程安全的 如果是局部变量引用了对象并逃离了方法的作用范围那就要考虑线程安全问题。 m1是线程安全的m2,m3都不算线程安全的。
栈内存溢出
栈帧过大、过多、或者第三方类库操作都有可能造成栈内存溢出 java.lang.stackOverflowError 使用 -Xss256k 指定栈内存大小
public class Demo1_19 {public static void main(String[] args) throws JsonProcessingException {Dept d new Dept();d.setName(Market);Emp e1 new Emp();e1.setName(zhang);e1.setDept(d);Emp e2 new Emp();e2.setName(li);e2.setDept(d);d.setEmps(Arrays.asList(e1, e2));// { name: Market, emps: [{ name:zhang, dept:{ name:, emps: [ {}]} },] }ObjectMapper mapper new ObjectMapper();System.out.println(mapper.writeValueAsString(d));}
}class Emp {private String name;JsonIgnoreprivate Dept dept;public String getName() {return name;}public void setName(String name) {this.name name;}public Dept getDept() {return dept;}public void setDept(Dept dept) {this.dept dept;}
}
class Dept {private String name;private ListEmp emps;public String getName() {return name;}public void setName(String name) {this.name name;}public ListEmp getEmps() {return emps;}public void setEmps(ListEmp emps) {this.emps emps;}
}线程运行诊断
案例一cpu 占用过多 解决方法Linux 环境下运行某些程序的时候可能导致 CPU 的占用过高这时需要定位占用 CPU 过高的线程
top 命令查看是哪个进程占用 CPU 过高ps H -eo pid, tid线程id, %cpu | grep 刚才通过 top 查到的进程号 通过 ps 命令进一步查看是哪个线程占用 CPU 过高jstack 进程 id 通过查看进程中的线程的 nid 刚才通过 ps 命令看到的 tid 来对比定位注意 jstack 查找出的线程 id 是 16 进制的需要转换。
本地方法栈 一些带有 native 关键字的方法就是需要 JAVA 去调用本地的C或者C方法因为 JAVA 有时候没法直接和操作系统底层交互所以需要用到本地方法栈服务于带 native 关键字的方法。
4、堆
定义
Heap 堆
通过new关键字创建的对象都会被放在堆内存
特点
它是线程共享堆内存中的对象都需要考虑线程安全问题有垃圾回收机制
堆内存溢出
java.lang.OutofMemoryError java heap space. 堆内存溢出 可以使用 -Xmx8m 来指定堆内存大小。
堆内存诊断
jps 工具 查看当前系统中有哪些 java 进程 jmap 工具 查看堆内存占用情况 jmap - heap 进程id jconsole 工具 图形界面的多功能的监测工具可以连续监测
5、方法区
定义
Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区域。方法区域类似于用于传统语言的编译代码的存储区域或者类似于操作系统进程中的“文本”段。它存储每个类的结构例如运行时常量池、字段和方法数据以及方法和构造函数的代码包括特殊方法用于类和实例初始化以及接口初始化方法区域是在虚拟机启动时创建的。
组成 方法区内存溢出
1.8 之前会导致永久代内存溢出 使用 -XX:MaxPermSize8m 指定永久代内存大小 1.8 之后会导致元空间内存溢出 使用 -XX:MaxMetaspaceSize8m 指定元空间大小
运行时常量池
二进制字节码包含类的基本信息常量池类方法定义包含了虚拟机的指令 首先看看常量池是什么编译如下代码
public class Test {public static void main(String[] args) {System.out.println(Hello World!);}}
然后使用 javap -v Test.class 命令反编译查看结果。 常量池 就是一张表虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息 运行时常量池 常量池是 *.class 文件中的当该类被加载以后它的常量池信息就会放入运行时常量池并把里面的符号地址变为真实地址
6、StringTable
常量池中的字符串仅是符号只有在被用到时才会转化为对象利用串池的机制来避免重复创建字符串对象字符串变量拼接的原理是StringBuilder字符串常量拼接的原理是编译器优化可以使用intern方法主动将串池中还没有的字符串对象放入串池中
例1
// StringTable [ a, b ,ab ] hashtable 结构不能扩容
public class Demo1_22 {// 常量池中的信息都会被加载到运行时常量池中 这时 a b ab 都是常量池中的符号还没有变为 java 字符串对象// ldc #2 会把 a 符号变为 a 字符串对象// ldc #3 会把 b 符号变为 b 字符串对象// ldc #4 会把 ab 符号变为 ab 字符串对象public static void main(String[] args) {String s1 a; // 懒惰的String s2 b;String s3 ab;String s4 s1 s2; // new StringBuilder().append(a).append(b).toString() new String(ab)String s5 a b; // javac 在编译期间的优化结果已经在编译期确定为abSystem.out.println(s3 s5);//trueSystem.out.println(s3 s4);//flase}
}intern方法 1.8
调用字符串对象的 intern 方法会将该字符串对象尝试放入到串池中
如果串池中没有该字符串对象则放入成功 如果有该字符串对象则放入失败无论放入是否成功都会返回串池中的字符串对象
注意此时如果调用 intern 方法成功堆内存与串池中的字符串对象是同一个对象如果失败则不是同一个对象
public class Demo1_23 {// [ab, a, b]public static void main(String[] args) {String x ab;String s new String(a) new String(b);// 堆 new String(a) new String(b) new String(ab)String s2 s.intern(); // 将这个字符串对象尝试放入串池如果有则并不会放入如果没有则放入串池 会把串池中的对象返回System.out.println( s2 x);//trueSystem.out.println( s x );//false}}面试题
/*** 演示字符串相关面试题*/
public class Demo1_21 {public static void main(String[] args) {String s1 a;String s2 b;String s3 a b; // abString s4 s1 s2; // new String(ab)String s5 ab;String s6 s4.intern();// 问System.out.println(s3 s4); // falseSystem.out.println(s3 s5); // trueSystem.out.println(s3 s6); // trueString x2 new String(c) new String(d); // new String(cd)
// x2.intern();String x1 cd;x2.intern();
// 问如果调换了【最后两行代码】的位置呢如果是jdk1.6呢System.out.println(x1 x2);//false}
}理解intern方法intern是将字符串放到常量池里常量池如果没有就把自己的地址放到常量池如果有就返回常量池里面的对象地址。
StringTable位置
jdk1.6 StringTable 位置是在永久代中1.8 StringTable 位置是在堆中。
StringTable 垃圾回收
-Xmx10m 指定堆内存大小 -XX:PrintStringTableStatistics 打印字符串常量池信息 -XX:PrintGCDetails -verbose:gc 打印 gc 的次数耗费时间等信息
/*** 演示 StringTable 垃圾回收* -Xmx10m -XX:PrintStringTableStatistics -XX:PrintGCDetails -verbose:gc*/
public class Code_05_StringTableTest {public static void main(String[] args) {int i 0;try {for(int j 0; j 10000; j) { // j 100, j 10000String.valueOf(j).intern();i;}}catch (Exception e) {e.printStackTrace();}finally {System.out.println(i);}}}
StringTable调优
在介绍性能调优之前不得不说一说StringTable的底层实现前面已经提到了StringTable底层是一个HashTableHashTable长什么样呢其实就是数组链表每个元素是一个key-value。当存入一个元素的时候就会将其key通过hash函数计算得出数组的下标并存放在对应的位置。 比如现在有一个key-value这个key通过hash函数计算结果为2那么就把value存放在数组下标为2的位置。但是如果现在又有一个key通过hash函数计算出了相同的结果比如也是2但2的位置已经有值了这种现象就叫做哈希冲突怎么解决呢这里采用了链表法 链表法就是将下标一样的元素通过链表的形式串起来如果数组容量很小但是元素很多那么发生哈希冲突的概率就会提高。大家都知道链表的效率远没有数组那么高哈希冲突过多会影响性能。所以为了减少哈希冲突的概率所以可以适当的增加数组的大小。数组的每一格在StringTable中叫做bucket我们可以增加bucket的数量来提高性能默认的数量为60013个来看一个对比
long startTime System.nanoTime();
String str hello;
for(int i 0;i 500000;i) {String s str i;s.intern();
}
long endTime System.nanoTime();
System.out.println(花费的时间为(endTime-startTime)/1000000 毫秒);先通过一个虚拟机参数将bucket指定的小一点来个2000吧
-XX:StringTableSize2000运行一下 一共花费了1.2秒。再来将bucket的数量增加一点来个20000个
-XX:StringTableSize20000运行一下 可以看到这次只花了0.19秒性能有了明显的提升说明这样确实可以优化StringTable。
7、直接内存
定义
Direct Memory
常见于 NIO 操作时用于数据缓冲区分配回收成本较高但读写性能高不受 JVM 内存回收管理
使用直接内存的好处
文件读写流程 因为 java 不能直接操作文件管理需要切换到内核态使用本地方法进行操作然后读取磁盘文件会在系统内存中创建一个缓冲区将数据读到系统缓冲区 然后在将系统缓冲区数据复制到 java 堆内存中。缺点是数据存储了两份在系统内存中有一份java 堆中有一份造成了不必要的复制。
使用了 DirectBuffer 文件读取流程 直接内存是操作系统和 Java 代码都可以访问的一块区域无需将代码从系统内存复制到 Java 堆内存从而提高了效率。
使用ByteBuffer.allocateDirect
/*** IO:阻塞式 NIO (New IO / Non-Blocking IO):非阻塞式* byte[] / char[] Buffer* Stream Channel** 查看直接内存的占用与释放*/
public class BufferTest {private static final int BUFFER 1024 * 1024 * 1024; //1GBpublic static void main(String[] args){//直接分配本地内存空间ByteBuffer byteBuffer ByteBuffer.allocateDirect(BUFFER);System.out.println(直接内存分配完毕请求指示);Scanner scanner new Scanner(System.in);scanner.next();System.out.println(直接内存开始释放);byteBuffer null;System.gc();scanner.next();}}使用unsafe类
/*** 直接内存分配的底层原理Unsafe*/
public class Demo1_27 {static int _1Gb 1024 * 1024 * 1024;public static void main(String[] args) throws IOException {Unsafe unsafe getUnsafe();// 分配内存long base unsafe.allocateMemory(_1Gb);unsafe.setMemory(base, _1Gb, (byte) 0);System.in.read();// 释放内存unsafe.freeMemory(base);System.in.read();}public static Unsafe getUnsafe() {try {Field f Unsafe.class.getDeclaredField(theUnsafe);f.setAccessible(true);Unsafe unsafe (Unsafe) f.get(null);return unsafe;} catch (NoSuchFieldException | IllegalAccessException e) {throw new RuntimeException(e);}}
}8、垃圾回收
如何判断对象可以回收
引用计数法
当一个对象被引用时引用对象的值加一当一个对象不被引用时引用对象的值减一当值为 0 时就表示该对象不被引用可以被垃圾收集器回收。 这个引用计数法听起来不错但是有一个弊端如下图所示循环引用时两个对象的计数都为1导致两个对象都无法被释放造成内存泄露。
可达性分析算法
JVM 中的垃圾回收器通过可达性分析来探索所有存活的对象扫描堆中的对象看能否沿着 GC Root 对象为起点的引用链找到该对象如果找不到则表示可以回收可以作为 GC Root 的对象 虚拟机栈栈帧中的本地变量表中引用的对象。方法区中类静态属性引用的对象方法区中常量引用的对象本地方法栈中 JNI即一般说的Native方法引用的对象已启动且未停止的 Java 线程
public static void main(String[] args) throws IOException {ArrayListObject list new ArrayList();list.add(a);list.add(b);list.add(1);System.out.println(1);System.in.read();list null;System.out.println(2);System.in.read();System.out.println(end);}
对于以上代码可以使用如下命令将堆内存信息转储成一个文件然后使用 Eclipse Memory Analyzer 工具进行分析。 第一步 使用 jps 命令查看程序的进程 第二步 使用 jmap -dump:formatb,live,file1.bin 16104 命令转储文件 dump转储文件 formatb二进制文件 file文件名 16104进程的id 第三步打开 Eclipse Memory Analyzer 对 1.bin 文件进行分析。 分析的 gc root找到了 ArrayList 对象然后将 list 置为null再次转储那么 list 对象就会被回收。
java定义常量是在方法区还是堆区
在Java中常量通常被定义为静态final字段它们被存储在方法区中的运行时常量池中。这意味着它们在程序运行期间只被分配一次并且可以被所有对象共享。堆区是用于存储对象实例的区域而不是常量。
四种引用 强引用 只有所有 GC Roots 对象都不通过【强引用】引用该对象该对象才能被垃圾回收
软引用SoftReference 仅有软引用引用该对象时在垃圾回收后内存仍不足时会再次出发垃圾回收回收软引用对象可以配合引用队列来释放软引用自身
弱引用WeakReference 仅有弱引用引用该对象时在垃圾回收时无论内存是否充足都会回收弱引用对象 可以配合引用队列来释放弱引用自身
虚引用PhantomReference 必须配合引用队列使用主要配合 ByteBuffer 使用被引用对象回收时会将虚引用入队由 Reference Handler 线程调用虚引用相关方法释放直接内存
终结器引用FinalReference 无需手动编码但其内部配合引用队列使用在垃圾回收时终结器引用入队被引用对象暂时没有被回收再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法第二次 GC 时才能回收被引用对象。
演示软引用
/*** 演示 软引用* -Xmx20m -XX:PrintGCDetails -verbose:gc*/
public class Code_08_SoftReferenceTest {public static int _4MB 4 * 1024 * 1024;public static void main(String[] args) throws IOException {method2();}// 设置 -Xmx20m , 演示堆内存不足,public static void method1() throws IOException {ArrayListbyte[] list new ArrayList();for(int i 0; i 5; i) {list.add(new byte[_4MB]);}System.in.read();}// 演示 软引用public static void method2() throws IOException {ArrayListSoftReferencebyte[] list new ArrayList();for(int i 0; i 5; i) {SoftReferencebyte[] ref new SoftReference(new byte[_4MB]);System.out.println(ref.get());list.add(ref);System.out.println(list.size());}System.out.println(循环结束 list.size());for(SoftReferencebyte[] ref : list) {System.out.println(ref.get());}}
}
method1 方法解析 首先会设置一个堆内存的大小为 20m然后运行 mehtod1 方法会抛异常堆内存不足因为 mehtod1 中的 list 都是强引用。 method2 方法解析 在 list 集合中存放了 软引用对象当内存不足时会触发 full gc将软引用的对象回收。细节如图 上面的代码中当软引用引用的对象被回收了但是软引用还存在所以一般软引用需要搭配一个引用队列一起使用。 修改 method2 如下 // 演示 软引用 搭配引用队列 public static void method3() throws IOException {ArrayListSoftReferencebyte[] list new ArrayList();// 引用队列ReferenceQueuebyte[] queue new ReferenceQueue();for(int i 0; i 5; i) {// 关联了引用队列当软引用所关联的 byte[] 被回收时软引用自己会加入到 queue 中去SoftReferencebyte[] ref new SoftReference(new byte[_4MB], queue);System.out.println(ref.get());list.add(ref);System.out.println(list.size());}// 从队列中获取无用的 软引用对象并移除Reference? extends byte[] poll queue.poll();while(poll ! null) {list.remove(poll);poll queue.poll();}System.out.println();for(SoftReferencebyte[] ref : list) {System.out.println(ref.get());}}弱引用
/*** 演示弱引用* -Xmx20m -XX:PrintGCDetails -verbose:gc*/
public class Demo2_5 {private static final int _4MB 4 * 1024 * 1024;public static void main(String[] args) {// list -- WeakReference -- byte[]ListWeakReferencebyte[] list new ArrayList();for (int i 0; i 10; i) {WeakReferencebyte[] ref new WeakReference(new byte[_4MB]);list.add(ref);for (WeakReferencebyte[] w : list) {System.out.print(w.get() );}System.out.println();}System.out.println(循环结束 list.size());}
}