java程序是怎么运行的?
首先就是从源文件开始编译成class字节码文件,然后jvm创建内存区域,开启main线程。
在类加载器生成,并且创建第一个对象的时候。那么怎么指向元空间中的类信息?通过在堆内存中创建Class对象保存内存地址,然后指向元空间中的对象
当类加载器下面的对象全部都被gc的时候就会清空雷系信息。
from+to区,最小内存值其实就是求from
标记清除:标记那些不能被回收的,然后清除掉那些可以回收的。通过不能回收的root来找那些间接被引用的对象。root可以是局部变量引用也可以是静态变量的引用堆内存的对象。会出现什么问题?内存碎片
标记整理:标记不能回收的,清除之后移动那些没被回收的对象到连续内存防止标记清除带来的问题内存碎片
标记复制:准备两个内存块,一个是存入对象的,一个是空的用于复制,标记不能回收,把不能回收的存入空的块,然后交换两个from和to的指针。
为什么要gc?为什么要这么多垃圾回收器,他们之间的区别?
gc的目的是回收无用对象,防止内存碎片,加快分配速度
gc回收谁?堆
gc,how判断无用?可达性分析和三色标记法
gc的如何实现?通过各种垃圾回收器
gc的规模怎么变化?minor(新生代)、mixed(新生代和部分老年代)、full(老年代新生代)
新生代存活短和老年代存活长,不同的存活区那么就要使用不同的垃圾回收策略。假设都是堆在一个区上面导致的问题就是同时使用标记复制,如果区域中存活长的对象多,那么很多没有意义的重新复制。但是对于存活少的时候,那么复制的对象就会少,增加了效率。对于存活长的对象更推荐使用标记整理。
三色标记就是记录引用的情况,如果黑色说明引用处理完,如果是灰色就是未被处理,白色未处理。在gc的作用是什么?
G1的特点?
吞吐和响应时间兼顾,主要依靠三个阶段的工作原理。这里没有from和to了,只有survivor和eden区
工作原理
如果一直给线程池添加任务,而且线程池初始化的阻塞队列是无限大小就会导致内存溢出。那么内存溢出导致的问题就是整个程序停止运行。OutOfMemoryError thrown from the UncaughtExceptionHandler in thread “main”.这个异常的意思其实就是gc了,但是没有gc到很多对象说明任务对象都在被引用导致的内存溢出问题。
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
while(true){
service.submit(()->{
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
这里就是因为cache无限创建线程导致的线程
OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
// ExecutorService executor = Executors.newFixedThreadPool(2);
while (true) {
executor.submit(()->{
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
对象本身就占用了一部分的内存,如果查询的对象数量太多,100w条,那么就可能会导致最后内存溢出。
如果动态生成很多类,而且类加载器是个长期存活的对象,就会导致Meta空间里面类信息无法被回收,最后导致本地内存溢出。
// -XX:MaxMetaspaceSize=24m
// 模拟不断生成类, 但类无法卸载的情况
public class TestOomTooManyClass {
static GroovyShell shell = new GroovyShell();
public static void main(String[] args) {
AtomicInteger c = new AtomicInteger();
while (true) {
try (FileReader reader = new FileReader("script")) {
// GroovyShell shell = new GroovyShell();
shell.evaluate(reader);
System.out.println(c.incrementAndGet());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
三个阶段
调用Student.class的时候加载类对象成功。并且存在类对象存在于堆内存中,而且final的静态变量已经赋值了,在方法区的常量池中赋值。其它静态变量设置默认初始值,等到初始化的时候就会把静态变量赋值
public class TestLazy {
private Class<?> studentClass;
public static void main(String[] args) throws IOException {
System.out.println("未用到 Student");
System.in.read();
System.out.println(Student.class); // 关键代码1,会触发类加载
System.out.println("已加载 Student");
TestLazy testLazy = new TestLazy();
testLazy.studentClass = Student.class;
System.in.read();
Student stu = new Student(); // 关键代码2,会触发类初始化
System.out.println("已初始化 Student");
System.in.read();
}
}
主要是赋值阶段不同, static final在解析的阶段就已经进行赋值了,但是static还需要在初始化阶段的时候混合static块中集中进行赋值。这里的#x的意思就是在方法区运行时常量池中查找。也就是静态变量实际上存在方法区,开辟空间给静态变量赋值。
static final int c;//声明,创建堆内存空间给c
descriptor: I
flags: ACC_STATIC, ACC_FINAL
ConstantValue: int 153//并且直接赋值
static final int m;
descriptor: I
flags: ACC_STATIC, ACC_FINAL
ConstantValue: int 32768
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: bipush 119 //存入操作栈
2: putstatic #18 // #18方法区的常量池中的静态变量a赋值为119
5: getstatic #21 // java/lang/System.out:Ljava/io/PrintStream;取出静态类
8: ldc #27 // String Student.class init 取出常量池的字符串。
10: invokevirtual #29 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: sipush 136
16: putstatic #35 // Field b:I
19: new #4 // 创建#4方法区的那个Object对象
22: dup//复制引用,一个用于调用方法,一个用于存入堆内存初始化的n属性
23: invokespecial #3 // Method java/lang/Object."":()V
26: putstatic #38 // Field n:Ljava/lang/Object;
29: return
基本类型是直接复制一份到引用它的那个类的常量池上面。引用类型仍然要去访问类对象的属性,所以类需要进行初始化。可以看看下面部分代码
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: sipush 153 //复制一份直接取出赋值
6: invokevirtual #15 // Method java/io/PrintStream.println:(I)V
9: getstatic #21 // Field java/lang/System.in:Ljava/io/InputStream;
12: invokevirtual #25 // Method java/io/InputStream.read:()I
15: pop
16: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
19: ldc #31 // int 32768//复制一份直接取出赋值
21: invokevirtual #15 // Method java/io/PrintStream.println:(I)V
24: getstatic #21 // Field java/lang/System.in:Ljava/io/InputStream;
27: invokevirtual #25 // Method java/io/InputStream.read:()I
30: pop
31: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
34: getstatic #32 // Field 但是这个地方很明显就是直接访问Student的属性day03/loader/Student.n:Ljava/lang/Object;
37: invokevirtual #36 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
40: getstatic #21 // Field java/lang/System.in:Ljava/io/InputStream;
43: invokevirtual #25 // Method java/io/InputStream.read:()I
46: pop
47: return
public class TestFinal {
public static void main(String[] args) throws IOException {
System.out.println(Student.c); // c 是 final static 基本类型
System.in.read();
System.out.println(Student.m); // m 是 final static 基本类型
System.in.read();
System.out.println(Student.n); // n 是 final static 引用类型
System.in.read();
}
}
其实意思就是在链接,因为本文件里面只知道有A这个符号,但是没有A这个类文件,所以要等待类A也被加载的时候才能够链接上,给对应的class进行赋值,下面的B明显还没有创建,所以是unresolvedClass(参考程序员的自我修养链接部分)
类加载器加载类时优先问上一级。比如一个Student类,在app类加载器中准备加载的时候都会优先去问一下Extension加载没,能不能加载,然后Extension先去问BootStrap加载没能不能加载,然后才会查询自己的路径,再返回给App,如果没有app再在自己的类路径下查找。
不能,原因是
相当于就是root,局部变量或者是静态变量指向的对象
就是在引用中间增加了软引用,第一次回收不会回收,但是第二次内存不够就会回收软引用的
与软引用相似,但是第一次回收就会被回收掉.。
在ThreadLocalMap里面需要用到引用队列,因为key是弱引用但是value是强引用导致最后出现内存溢出。但是引用队列,可以取出这些引用并且进行清理操作。 像这种清理,就是删除了对象A,但是仍然有外部关联资源value,这个时候就要通过引用队列来进行删除
public class TestWeakReference {
public static void main(String[] args) {
MyWeakMap map = new MyWeakMap();
map.put(0, new String("a"), "1");
map.put(1, new String("b"), "2");
map.put(2, new String("c"), "3");
map.put(3, new String("d"), "4");
System.out.println(map);
System.gc();
System.out.println(map.get("a"));
System.out.println(map.get("b"));
System.out.println(map.get("c"));
System.out.println(map.get("d"));
System.out.println(map);
map.clean();
System.out.println(map);
}
// 模拟 ThreadLocalMap 的内存泄漏问题以及一种解决方法
static class MyWeakMap {
// static ReferenceQueue
static ReferenceQueue<Object> queue=new ReferenceQueue<>();
static class Entry extends WeakReference<String> {
String value;
public Entry(String key, String value) {
super(key, queue);
this.value = value;
}
}
public void clean() {
Object ref;
while ((ref = queue.poll()) != null) {
System.out.println(ref);
for(int i=0;i<table.length;i++){
if(table[i]==ref){
table[i]=null;
}
}
}
}
Entry[] table = new Entry[4];
public void put(int index, String key, String value) {
table[index] = new Entry(key, value);
}
public String get(String key) {
for (Entry entry : table) {
if (entry != null) {
String k = entry.get();
if (k != null && k.equals(key)) {
return entry.value;
}
}
}
return null;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (Entry entry : table) {
if (entry != null) {
String k = entry.get();
sb.append(k).append(":").append(entry.value).append(",");
}
}
if (sb.length() > 1) {
sb.deleteCharAt(sb.length() - 1);
}
sb.append("]");
return sb.toString();
}
}
}
能够独立线程处理清理工作,方便使用。一旦发现对象被清除,立刻清除对应的资源。
public class TestCleaner1 {
public static void main(String[] args) throws IOException {
Cleaner cleaner = Cleaner.create();
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 1"));
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 2"));
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 3"));
MyResource obj = new MyResource();
cleaner.register(obj, ()-> LoggerUtils.get().debug("clean 4"));
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 5"));
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 6"));
System.gc();
System.in.read();
}
static class MyResource {
}
}
一定要配合引用队列,为了在回收虚引用的对象之后,还要释放对象关联的外部资源
但是如果字符串是常量"b"那么就会有一个引用直接存入串池中,并且引用指向这个"b"的字符串对象,那么就不会清除了
public class TestPhantomReference {
public static void main(String[] args) throws IOException, InterruptedException {
ReferenceQueue<String> queue = new ReferenceQueue<>();// 引用队列
List<MyResource> list = new ArrayList<>();
list.add(new MyResource(new String("a"), queue));
list.add(new MyResource("b", queue));
list.add(new MyResource(new String("c"), queue));
System.gc(); // 垃圾回收
Thread.sleep(100);
Object ref;
while ((ref = queue.poll()) != null) {
if (ref instanceof MyResource resource) {
resource.clean();
}
}
}
static class MyResource extends PhantomReference<String> {
public MyResource(String referent, ReferenceQueue<? super String> q) {
super(referent, q);
}
// 释放外部资源的方法
public void clean() {
LoggerUtils.get().debug("clean");
}
}
}
补充这个是在垃圾回收器确定对象不使用的时候进行调用。而且线程是提前开启的,等待垃圾回收的信息。下面有个while循环。
//加入到队列
private Finalizer(Object finalizee) {
super(finalizee, queue);
// push onto unfinalized
synchronized (lock) {
if (unfinalized != null) {
this.next = unfinalized;
unfinalized.prev = this;
}
unfinalized = this;
}
}
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);//开启线程执行finalize
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, null, "Finalizer", 0, false);
}
public void run() {
// in case of recursive call to run()
if (running)
return;
// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (VM.initLevel() == 0) {
// delay until VM completes initialization
try {
VM.awaitInitLevel(1);
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
通过把对象放进Finalizer里面,然后连接成一个双向链表,加入到引用队列。