目录
两张一模一样的表,在master和slave中explain会有不同吗?为什么 ?
Java 中 final 作用是什么?
final
关键字主要有以下三个方面的作用:用于修饰类、方法和变量。
-
修饰类:当
final
修饰一个类时,表示这个类不能被继承,是类继承体系中的最终形态。例如,Java 中的String
类就是用final
修饰的,这保证了String
类的不可变性和安全性,防止其他类通过继承来改变String
类的行为和特性。 -
修饰方法:用
final
修饰的方法不能在子类中被重写。比如,java.lang.Object
类中的getClass
方法就是final
的,因为这个方法的行为是由 Java 虚拟机底层实现来保证的,不应该被子类修改。 -
修饰变量:当
final
修饰基本数据类型的变量时,该变量一旦被赋值就不能再改变。例如,final int num = 10;
,这里的num
就是一个常量,不能再对其进行重新赋值操作,否则会导致编译错误。对于引用数据类型,final
修饰意味着这个引用变量不能再指向其他对象,但对象本身的内容是可以改变的。例如,final StringBuilder sb = new StringBuilder("Hello");
,不能让sb
再指向其他StringBuilder
对象,但可以通过sb.append(" World");
来修改字符串的内容。
如何获取私有对象?
在 Java 中,私有对象通常指的是类中被声明为 private
的成员变量或方法。由于 private
访问修饰符的限制,这些成员只能在其所在的类内部被访问。
不过,可以通过下面两种方式来间接获取私有对象。
-
使用公共访问器方法(getter 方法):如果类的设计者遵循良好的编程规范,通常会为私有成员变量提供公共的访问器方法(即
getter
方法),通过调用这些方法可以安全地获取私有对象。
class MyClass {
// 私有成员变量
private String privateField = "私有字段的值";
// 公共的 getter 方法
public String getPrivateField() {
return privateField;
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
// 通过调用 getter 方法获取私有对象
String value = obj.getPrivateField();
System.out.println(value);
}
}
-
反射机制。反射机制允许在运行时检查和修改类、方法、字段等信息,通过反射可以绕过
private
访问修饰符的限制来获取私有对象。
import java.lang.reflect.Field;
class MyClass {
private String privateField = "私有字段的值";
}
public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
MyClass obj = new MyClass();
// 获取 Class 对象
Class<?> clazz = obj.getClass();
// 获取私有字段
Field privateField = clazz.getDeclaredField("privateField");
// 设置可访问性
privateField.setAccessible(true);
// 获取私有字段的值
String value = (String) privateField.get(obj);
System.out.println(value);
}
}
Jvm内存区有哪些?
根据 JDK 8 规范,JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分。还有一部分内存叫直接内存,属于操作系统的本地内存,也是可以直接操作的。
JVM的内存结构主要分为以下几个部分:
-
程序计数器:可以看作是当前线程所执行的字节码的行号指示器,用于存储当前线程正在执行的 Java 方法的 JVM 指令地址。如果线程执行的是 Native 方法,计数器值为 null。是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域,生命周期与线程相同。
-
Java 虚拟机栈:每个线程都有自己独立的 Java 虚拟机栈,生命周期与线程相同。每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。可能会抛出 StackOverflowError 和 OutOfMemoryError 异常。
-
本地方法栈:与 Java 虚拟机栈类似,主要为虚拟机使用到的 Native 方法服务,在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。本地方法执行时也会创建栈帧,同样可能出现 StackOverflowError 和 OutOfMemoryError 两种错误。
-
Java 堆:是 JVM 中最大的一块内存区域,被所有线程共享,在虚拟机启动时创建,用于存放对象实例。从内存回收角度,堆被划分为新生代和老年代,新生代又分为 Eden 区和两个 Survivor 区(From Survivor 和 To Survivor)。如果在堆中没有内存完成实例分配,并且堆也无法扩展时会抛出 OutOfMemoryError 异常。
-
方法区(元空间):在 JDK 1.8 及以后的版本中,方法区被元空间取代,使用本地内存。用于存储已被虚拟机加载的类信息、常量、静态变量等数据。虽然方法区被描述为堆的逻辑部分,但有 “非堆” 的别名。方法区可以选择不实现垃圾收集,内存不足时会抛出 OutOfMemoryError 异常。
-
运行时常量池:是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,具有动态性,运行时也可将新的常量放入池中。当无法申请到足够内存时,会抛出 OutOfMemoryError 异常。
-
直接内存:不属于 JVM 运行时数据区的一部分,通过 NIO 类引入,是一种堆外内存,可以显著提高 I/O 性能。直接内存的使用受到本机总内存的限制,若分配不当,可能导致 OutOfMemoryError 异常。
如何把方法区撑爆 ?
方法区主要存储类型相关信息
,在JDK 6及以前版本字符串常量池
也在此区域存储,JDK 7 时将字符串常量池移入堆内存。我们可以使字符串常量池
、类型相关信息
导致内存溢出。
在JDK 6及以前版本字符串常量池
存储在方法区,JDK 7 时将字符串常量池移入堆内存。所以此方法只能在jdk6生效。
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int strCount = 0;
while (true) {
list.add(String.valueOf(strCount++).intern());
}
}
还可以通过产生大量类来填满方法区,使其内存溢出。产生大量类有有多种方式,例如JDK提供的动态代理,第三方的GGLlib。
public static void main(String[] args) {
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(obj,objects);
}
});
enhancer.create();
}
}
static class OOMObject{
}
这段代码是一个典型的Java内存溢出(Out Of Memory)示例,它使用CGLib库创建无限个代理对象,从而导致内存不断增长,最终耗尽系统资源。
发生fullgc后,用什么去分析 ?
-
jstat:可以实时监控 Java 应用的各种运行状态,包括堆内存各区域的使用情况、GC 次数和时间等。例如,使用以下命令可以每隔 1 秒输出一次 GC 统计信息:
jstat -gc <pid> 1000
其中,<pid>
是 Java 进程的 ID。通过观察这些信息,可以了解 Full GC 前后堆内存的变化情况。
-
jmap:用于生成 Java 堆的内存快照(堆转储文件),可以在 Full GC 发生后使用该命令生成堆转储文件,以便后续使用 MAT 等工具进行详细分析。例如:
jmap -dump:format=b,file=heapdump.hprof <pid>
该命令会将 Java 堆的内存快照保存到heapdump.hprof
文件中。
-
jstack