java8以后字符串常量池的位置,以及元空间的探秘,使用VisualVM进行实战验证 文章结论:根据变化情况,推出字符串常量池在堆的old区,字符串在Young的Eden区产生,调用intern()就是先看old区有没有,如果没有将这个对象存入old区中
解决方案
使用JDK1.8
2.3未关闭的流操作
忘记关闭流是一种非常常见的情况,当然,大多数开发人员都可以涉及到这种情况
@Test(expected = OutOfMemoryError.class)
public void givenURL_whenUnclosedStream_thenOutOfMemory()
throws IOException, URISyntaxException {
String str = "";
URLConnection conn
= new URL("http://norvig.com/big.txt").openConnection();
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
while (br.readLine() != null) {
str += br.readLine();
}
//
}
引用文本正如我们所看到的,堆的使用随着时间的推移逐渐增加 - 这是由于未关闭流引起的内存泄漏的直接影响。
从技术上讲,未关闭的流将导致两种类型的泄漏–低级资源泄漏和内存泄漏。
低级资源泄漏只是操作系统级资源的泄漏-例如文件描述符,打开的连接等。这些资源也可能泄漏,就像内存一样。
当然,JVM也使用内存来跟踪这些基础资源,这就是为什么这也会导致内存泄漏的原因。
怎么解决
我们始终需要记住手动关闭流,或者利用Java 8中引入的自动关闭功能:
try (BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
// further implementation
} catch (IOException e) {
e.printStackTrace();
}
在这种情况下,BufferedReader将在try语句的末尾自动关闭,而无需在显式的finally块中将其关闭。
2.4. 未关闭的连接
这种情况与前一种情况非常相似,主要区别在于处理未关闭的连接(例如,到数据库,到FTP服务器等)。 再次,不正确的实现可能造成很大的危害,从而导致内存问题。
让我们来看一个简单的例子:
@Test(expected = OutOfMemoryError.class)
public void givenConnection_whenUnclosed_thenOutOfMemory()
throws IOException, URISyntaxException {
URL url = new URL("ftp://speedtest.tele2.net");
URLConnection urlc = url.openConnection();
InputStream is = urlc.getInputStream();
String str = "";
//
}
URLConnection保持打开状态,结果是可以预料的是内存泄漏
请注意,垃圾收集器无法执行任何操作来释放未使用但有引用的内存。
如何解决
当然是关闭连接
2.5在HashSet中加入没有重写hashcode()和equals()的对象
一个可能导致内存泄漏的简单但非常常见的示例是将**未重写hashCode()或equals()**的对象HashSet与一起使用。
当我们将未重写hashcode()和equals()的对象加入HashSet中,HashSet会不断增长,而不是应该忽略重复
添加后,我们也将无法删除这些对象。
让我们创建一个不包含equals或hashCode的简单类:
public class Key {
public String key;
public Key(String key) {
Key.key = key;
}
}
@Test(expected = OutOfMemoryError.class)
public void givenMap_whenNoEqualsNoHashCodeMethods_thenOutOfMemory()
throws IOException, URISyntaxException {
Map<Object, Object> map = System.getProperties();
while (true) {
map.put(new Key("key"), "value");
}
}
请注意,垃圾收集器在1:40停止了回收内存的操作,我们注意到了内存泄漏; 此后,GC收集数量几乎下降了四倍。
如何解决
在这种情况下,解决方案很简单-提供hashCode()和equals()实现至关重要。
这里值得一提的工具是Lombok项目-它通过注释提供了许多默认实现,例如 @EqualsAndHashCode。
2.6[补充] 引用外部类的内部类
内部类和静态内部类简单介绍
内部类:就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)
静态内部类:就是我跟你没关系,自己可以完全独立存在,但是我就借你的壳用一下,来隐藏自己。
总而言之,每一个非静态内部类都有一个对包含它的类的隐式引用,就算外部类对象没用了也内部类也GC不掉
但是静态内部类没有。
考虑一个类,它声明了大量复杂笨重类的字段
现在,我们创建一个内部类,因为非静态内部类无法脱离外部类,所以需要先实例化外部类,通过外部类实例化内部类
Outer outer = new Outer("Nested"); // 实例化一个Outer
Outer.Inner inner = outer.new Inner(); // 实例化一个Inner
inner.hello();
实例化一个Inner Class不能脱离Outer实例。
现在内存模型长这样
但是如果我们使用静态内部类
发生这种情况是因为内部类对象隐式保留对外类对象的引用,从而使其成为垃圾收集的无效候选者。
在匿名类的情况下也是如此
。例如
public class Main {
public static void main(String[] args) {
Outer outer = new Outer("Nested");
outer.asyncHello();
}
}
class Outer {
private String name;
Outer(String name) {
this.name = name;
}
void asyncHello() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, " + Outer.this.name);
}
};
new Thread(r).start();
}
}
观察asyncHello()方法,我们在方法内部实例化了一个Runnable。Runnable本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable接口的匿名类,并且通过new实例化该匿名类,然后转型为Runnable。
如何解决
在不使用外部类非静态成员变量的情况下,考虑使用静态内部类
参考地址: