深入理解JVM
类的使用方式
类的初始化:JVM只会在首次主动使用一个类或接口时,初始化它们。
主动使用
主动使用有以下方式
1. 使用new创建对象
package init;
public class Test1 {
static{
System.out.println("Test1……");
}
public static void main(String[] args) {
new Test1();
}
}
执行结果:
Test1……
2. 使用(包括取值和赋值)类的静态属性(成员变量、方法)
package init;
class MyTest{
static int a = 12;
static {
System.out.println("MyTest……");
}
static void method(){
System.out.println("method……");
}
}
public class Test3 {
public static void main(String[] args) {
System.out.println(MyTest.a);
}
}
执行结果:
MyTest……
12
package init;
class MyTest{
static int a = 12;
static {
System.out.println("MyTest……");
}
static void method(){
System.out.println("method……");
}
}
public class Test3 {
public static void main(String[] args) {
MyTest.method();
}
}
执行结果:
MyTest……
method……
注意:
- 使用static和final同时修饰的变量是不初始化类。如下
package init;
import java.util.Random;
class Test{
public static final int b = 12;
public static final int c = new Random().nextInt();
static {
System.out.println("Test……");
}
}
public class Test2 {
public static void main(String[] args) {
System.out.println(Test.b);//只调用Test.b 结果为12
}
}
- 但是如果常量的值是一个随机值,则常量所在类会被初始化。这是因为c是随机数生成,是要进行计算的,而在类的加载和链接阶段是无法对其进行计算的,所以得进行初始化才能得到准确的值。
package init;
import java.util.Random;
class Test{
public static final int b = 12;
public static final int c = new Random().nextInt();
static {
System.out.println("Test……");
}
}
public class Test2 {
public static void main(String[] args) {
System.out.println(Test.c);
}
}
执行结果:
Test……
-673947267
可以查看下面Test2的字节码文件,更容易帮助理解。可以发现Test.c是在 Test2的main的方法中的。并且此时的值还未确定下来。而是在进行初始化才能得到准确的值
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package init;
public class Test2 {
public Test2() {
}
public static void main(String[] args) {
System.out.println(Test.c);
}
}
3.使用Class.forName()执行反射时使用的类
package init;
class B{
static {
System.out.println("B……");
}
}
public class A {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("init.B");
}
}
执行结果:
B……
4.初始化一个子类,该子类的父类也会被初始化
Father.java
package init;
public class Father {
static {
System.out.println("Father……");
}
}
Son.java
package init;
public class Son extends Father{
public static void main(String[] args) {
new Son();
}
}
执行结果:
Father……
5.动态语言在执行时所涉及的类 也会 被 初始化(动态代理)
被动使用
除了主动以外,其他都是被动使用
package init;
class BD
{
static {
System.out.println("BD...");
}
}
public class BeiDong {
public static void main(String[] args) {
BD[] bds = new BD[3];
}
}
以上代码,不属于主动使用类,因此不会被初始化。
主动使用中静态成员的问题
package mystatic;
class Father{
static int i = 12;
static {
System.out.println("Father……");
}
}
class Son extends Father{
static {
System.out.println("Son……");
}
}
public class Test {
public static void main(String[] args) {
System.out.println( Son.i );
}
}
执行结果:
Father……
12
因为Son中对i没有定义,i是Father的静态变量。所以不会对Son进行类的初始化。
package mystatic;
class Father{
//static int i = 12;
static int i = (int) Math.random()*1000;
static {
System.out.println("Father……");
}
}
class Son extends Father{
static {
System.out.println("Son……");
}
}
public class Test {
public static void main(String[] args) {
System.out.println( Son.i );
}
}
执行结果:
Father……
0
static int i = (int) Math.random()*1000;没有加final字段,效果同上。
package mystatic;
class Father{
//static int i = 12;
//static int i = (int) Math.random()*1000;
static final int i = (int) Math.random() * 1000;
static {
System.out.println("Father……");
}
}
class Son extends Father{
static {
System.out.println("Son……");
}
}
public class Test {
public static void main(String[] args) {
System.out.println( Son.i );
}
}
执行结果:
Father……
0
常量的值是一个随机值,则常量所在类会被初始化。
package mystatic;
class Father{
//static int i = 12;
//static int i = (int) Math.random()*1000;
static final int i = (int) Math.random() * 1000;
static {
System.out.println("Father……");
}
}
class Son extends Father{
static int i = 102;
static {
System.out.println("Son……");
}
}
public class Test {
public static void main(String[] args) {
System.out.println( Son.i );
}
}
执行结果:
Father……
Son……
102
i是son的静态变量所以会 初始化son。而father是son的父类。所以father也会被初始化。
助记符
反编译,进入到对应class目录中。使用javap命令进行反编译:
javap -c class文件名
一些符号:
aload_0: 装载一个引用类型
invokespecial:init。private,super.method() :< init >存放的是初始化代码的位置
getsattic:获取静态成员
bipush: 数据范围是-128-127之间(8位带符号的整数),放到栈顶
sipush: 数据范围大于127(16位带符号的整数),放到栈顶
无论数据类型定义的是int、short等,只要数据范围是-128-127那么对应反编译指令都是bipush,否则是sipush
注意:
-1 到5不是 bipush。而分别是 iconst_m1 、iconst_0 iconst_1 iconst2…… iconst_5
ldc: int float String常量,放到栈顶
ldc2_w: long double 常量,放到栈顶
JVM四种常用引用级别
GC会回收哪些对象呢? 这就要根据引用级别来判断了。
JMV中存在四种引用级别,其强弱关系为:强引用 > 软引用 > 弱引用 > 虚引用
各种引用的出处:
强引用:用new
软引用、弱引用、虚引用:Reference
强引用
Object obj = new Object();
强引用对象new Object在以下情况失效
- 生命周期结束(作用域失效)
public void method(){
Object obj = new Object();
}//当执行完method方法后,强引用所指向的 引用对象 new Object() 就会等待GC的回收
- 引用被置为null,则引用对象等待GC的回收
obj = null;//此时,没有任何引用指向引用对象new Object() ,则引用对象会 等待GC的回收
除了上述两种情况,其他情况即使JVM OutOfMemory也不会回收强引用对象。
软引用
根据JVM的内存情况:如果内存充足,GC不会随便地回收软引用对象;如果JVM内存不足,则GC就会主动回收引用对象。
package ref;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
class SoftObject{
}
public class SoftRefDemo {
public static void main(String[] args) throws InterruptedException {
//装饰模式
SoftReference<SoftObject> softRef = new SoftReference<SoftObject>(new SoftObject());
new Thread(()->{
while(true){
//线程休眠 让 softRef有机会同步
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if( softRef.get()== null){// 指向指向SoftObject对象的软引用对象
System.out.println("软引用对象已经被回收");
System.exit(0);
}
}
}).start();
List<byte[]> list = new ArrayList<>();
//不断添加数组使得JVM内存溢出
while( true ){
Thread.sleep(100);
list.add(new byte[1024 * 1024]);
}
}
}
执行结果:
Exception in thread “main” 软引用对象已经被回收
弱引用
回收时机: 只要GC执行,就将弱引用对象进行回收。
package ref;
import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
public static void main(String[] args) throws Exception {
WeakReference<Object> weakRef = new WeakReference<>(new Object());
//weakRef->Object
System.out.println( weakRef.get()==null ? "已被回收":"没被回收" );
System.gc();//建议GC执行一次回收(存在概率)
Thread.sleep(100);
System.out.println( weakRef.get()==null ? "已被回收":"没被回收" );
}
}
执行结果:
未被回收
已被回收
虚引用
虚引用对应类java.lang.ref.PhantomReference< T >
是否使用虚引用,和引用对象本身没有任何关系;无法通过虚引用来获取对象本身。
虚引用对象的get() 返回值是null。所以虚引用不会单独使用,一般和引用队列一起使用。
意义;
当gc回收一个对象时,如果gc发现此对象还有一个虚引用就会将虚引用放入引用队列中,当虚引用对象出队之后才去回收该对象。因此,我们可以使用虚引用+引用对象实现,对象呗gc回收之前
执行一些额外操作。
package ref;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
class MyObject {
}
public class PhantomReferenceDemo {
public static void main(String[] args) throws Exception {
MyObject obj = new MyObject();
//引用队列
ReferenceQueue queue = new ReferenceQueue();
//虚引用+引用队列
PhantomReference<MyObject> phantomRef = new PhantomReference<>(obj, queue);
//让gc执行一次回收操作
obj = null;
System.gc();
Thread.sleep(30);
System.out.println("GC执行...");
//GC-> 虚引用->入队->出队-> obj
System.out.println(queue.poll());
}
}
执行结果:
GC执行…
java.lang.ref.PhantomReference@10f87f48
最终引用(了解即可)
在Java中存在Finalizer(final class Finalizer extends FinalReference)帮我们自动回收一些不需要的对象。jvm不能操作 直接内存(非jvm操作的内容)时,而恰好 此区域的内容 又忘了关闭,此时Finalizer就会将这些内存进行回收。
使用软引用实现缓存的淘汰策略
package ref;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
class Item{
}
public class SoftDemo {
static Map<String, SoftReference<Item>> caches = new HashMap<>();
//set从db获取数据到缓存中
void setCaches(String id, Item item){
caches.put(id, new SoftReference<Item>(item));
}
//get从缓存中获取数据到java代码中
Item getCaches(String id){
SoftReference<Item> soft = caches.get(id);
//soft为空则表示被GC回收了
//否则直接调用soft.get() 获取对象本身
return soft == null ? null : soft.get();
}
public static void main(String[] args) {
}
}