一篇翻译自国外的译文翻译:java永久代去哪儿了

Java永久代去哪儿了 - CSDN博客
Java永久代去哪儿了
在Java虚拟机(以下简称JVM)中,类包含其对应的元数据,比如类的层级信息,方法数据和方法信息(如字节码,栈和变量大小),运行时常量池,已确定的符号引用和虚方法表。
在过去(当自定义类加载器使用不普遍的时候),类几乎是“静态的”并且很少被卸载和回收,因此类也可以被看成“永久的”。另外由于类作为JVM实现的一部分,它们不由程序来创建,因为它们也被认为是“非堆”的内存。
在JDK8之前的HotSpot虚拟机中,类的这些“永久的”数据存放在一个叫做永久代的区域。永久代一段连续的内存空间,我们在JVM启动之前可以通过设置-XX:MaxPermSize的值来控制永久代的大小,32位机器默认的永久代的大小为64M,64位的机器则为85M。永久代的垃圾回收和老年代的垃圾回收是绑定的,一旦其中一个区域被占满,这两个区都要进行垃圾回收。但是有一个明显的问题,由于我们可以通过-XX:MaxPermSize
设置永久代的大小,一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出错误(OOM)。
备注:在JDK7之前的HotSpot虚拟机中,纳入字符串常量池的字符串被存储在永久代中,因此导致了一系列的性能问题和内存溢出错误。想要了解这些永久代移除这些字符串的信息,请访问这里查看。
辞永久代,迎元空间
随着Java8的到来,我们再也见不到永久代了。但是这并不意味着类的元数据信息也消失了。这些数据被移到了一个与堆不相连的本地内存区域,这个区域就是我们要提到的元空间。
这项改动是很有必要的,因为对永久代进行调优是很困难的。永久代中的元数据可能会随着每一次Full GC发生而进行移动。并且为永久代设置空间大小也是很难确定的,因为这其中有很多影响因素,比如类的总数,常量池的大小和方法数量等。
同时,HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full GC以及对以后的并发隔离类元数据等方面进行优化。
移除永久代的影响
由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。因此,我们就不会遇到永久代存在时的内存溢出错误,也不会出现泄漏的数据移到交换区这样的事情。最终用户可以为元空间设置一个可用空间最大值,如果不进行设置,JVM会自动根据类的元数据大小动态增加元空间的容量。
注意:永久代的移除并不代表自定义的类加载器泄露问题就解决了。因此,你还必须监控你的内存消耗情况,因为一旦发生泄漏,会占用你的大量本地内存,并且还可能导致交换区交换更加糟糕。
元空间内存管理
元空间的内存管理由元空间虚拟机来完成。先前,对于类的元数据我们需要不同的垃圾回收器进行处理,现在只需要执行元空间虚拟机的C++代码即可完成。在元空间中,类和其元数据的生命周期和其对应的类加载器是相同的。话句话说,只要类加载器存活,其加载的类的元数据也是存活的,因而不会被回收掉。
我们从行文到现在提到的元空间稍微有点不严谨。准确的来说,每一个类加载器的存储区域都称作一个元空间,所有的元空间合在一起就是我们一直说的元空间。当一个类加载器被垃圾回收器标记为不再存活,其对应的元空间会被回收。在元空间的回收过程中没有重定位和压缩等操作。但是元空间内的元数据会进行扫描来确定Java引用。
元空间虚拟机负责元空间的分配,其采用的形式为组块分配。组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表。当一个类加载器需要组块时,它就会从这个全局的组块列表中获取并维持一个自己的组块列表。当一个类加载器不再存活,那么其持有的组块将会被释放,并返回给全局组块列表。类加载器持有的组块又会被分成多个块,每一个块存储一个单元的元信息。组块中的块是线性分配(指针碰撞分配形式)。组块分配自内存映射区域。这些全局的虚拟内存映射区域以链表形式连接,一旦某个虚拟内存映射区域清空,这部分内存就会返回给操作系统。
上图展示的是虚拟内存映射区域如何进行元组块的分配。类加载器1和3表明使用了反射或者为匿名类加载器,他们使用了特定大小组块。 而类加载器2和4根据其内部条目的数量使用小型或者中型的组块。
元空间调优与工具
正如上面提到的,元空间虚拟机控制元空间的增长。但是有些时候我们想限制其增长,比如通过显式在命令行中设置-XX:MaxMetaspaceSize。默认情况下,-XX:MaxMetaspaceSize的值没有限制,因此元空间甚至可以延伸到交换区,但是这时候当我们进行本地内存分配时将会失败。
对于一个64位的服务器端JVM来说,其默认的–XX:MetaspaceSize值为21MB。这就是初始的高水位线。一旦触及到这个水位线,Full GC将会被触发并卸载没有用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,这个高水位线则上升。如果释放空间过多,则高水位线下降。如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志我们可以观察到Full
GC多次调用。为了避免频繁的GC,建议将–XX:MetaspaceSize设置为一个相对较高的值。
经过多次GC之后,元空间虚拟机自动调节高水位线,以此来推迟下一次垃圾回收到来。
有这样两个选项 -XX:MinMetaspaceFreeRatio和-XX:MaxMetaspaceFreeRatio,他们类似于GC的FreeRatio选项,用来设置元空间空闲比例的最大值和最小值。我们可以通过命令行对这两个选项设置对应的值。
下面是一些改进的工具,用来获取更多关于元空间的信息。
jmap -clstats PID 打印类加载器数据。(-clstats是-permstat的替代方案,在JDK8之前,-permstat用来打印类加载器的数据)。
jstat -gc LVMID 用来打印元空间的信息。具体内容如下:
jcmd PID GC.class_stats 一个新的诊断命令,用来连接到运行的JVM并输出详尽的类元数据的柱状图。
存在的问题
前面已经提到,元空间虚拟机采用了组块分配的形式,同时区块的大小由类加载器类型决定。类信息并不是固定大小,因此有可能分配的空闲区块和类需要的区块大小不同,这种情况下可能导致碎片存在。元空间虚拟机目前并不支持压缩操作,所以碎片化是目前最大的问题。
本文已收录于以下专栏:
相关文章推荐
本文来自:曹胜欢博客专栏。转载请注明出处:http://blog.csdn.net/csh
写在前面:由于前天项目老师建设局的项目快到验收阶段,所以,前天晚上通宵,昨天睡了大...
利用spring访问数据库是我们ssh程序中必不可少的步骤,在没有hibernate之前,我们一般都用jdbc访问数据库,所以用jdbc访问数据库必不可少的要进行一些配置,spring中为我们提供了访...
这是一个使用springMVC+spring+mybatis+bootstrap table做的分页流程,网上之前搜到的类似的教程实在太少了,所以就想着写一个大体的过程(没写的很详细),能帮助到一些人...
在日常开发中,常用的webservice引擎主要有Axis,axis2,Xfire以及cxf(Xfire的升级版)。现在只有axis2和cxf官方有更新。今天我们先以一个比较老的引擎为例。来讲诉一下w...
本文来自:曹胜欢博客专栏。转载请注明出处:http://blog.csdn.net/csh
一:首先从枚举开始说起
枚举类型是JDK5.0的新特征。Sun引进了一个全新的关...
在我们学习spring之前,根据spring的特性,我们来自己来模拟一个spring出来,也就是说不利用spring来实现spring的效果。本实例主要是实现spring的IOC功能。
名称(站点名或人名)
Adam Bien
Java EE相关
Antonio Goncalves
        
        笔者当初为了学习JAVA,收集了很多经典源码,源码难易程度分为初级、中级、高级等,详情看源码列表,需要的...
一对一关系映射即为关系双方都含有对方一个引用,其实在生活中一对一关系也很常见,比如人和身份证,学生和学号等,都是一对一的关系映射,一对一映射分为单向的和双向的,没种关系映射又可以分为主键关联映射,唯一...
他的最新文章
讲师:宋宝华
讲师:何宇健
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)Java编程题——在一个字符串中查找第一个非重复的字符 - ImportNew
编写一个Java程序来查找一个字符串中第一个非重复的字符,这是在编程测试中很常见的一个问题,因为字符串处理在程序员面试中是一个普遍的话题。面试前最好是准备好一些熟知的编程问题,例如使用递归反转字符串,或者检查一个字符串是否是回文(即正反读顺序一致)。查找第一个非重复字符的问题也是在同一个范畴。在给出解决方案之前,我们先来弄懂这个问题。我们需要编写一个函数,这个函数接受一个字符串作为参数,并返回第一个不重复的字符。例如字符串“hello”,除了“l”之外所有字符都是不重复的,但是“h”是第一个不重复的字符。同样,字符串“swiss”中“w”是第一个不重复的字符。一种解决该问题的方法就是创建一张表来记录每个字符的出现次数,然后选出第一个不重复的元素。关键在于字符的次序,你的代码必须返回第一个非重复的字符。另外,在这篇文章中,我们可以看到3个解决该问题的示例。我们的第一个方案是使用LinkedHashMap来记录字符个数,因为LinkedHashMap维持的元素顺序与插入顺序一致,而我们正是按照字符串中字符出现的顺序来将字符插入Map中的。当我们扫描字符串时,只需要迭代LinkedHashMap并找出值为1的元素。是的,这种方案只需要一个LinkedHashMap以及两个循环。我们的第二种解决方案是时间和空间的折衷,在一次遍历中找出不重复的字符。这次,我们使用一个Set和一个List去分开保存重复和不重复的字符。当我们完成一次时间复杂度为O(n)的字符串扫描后,我们可以通过访问List来获得这个神奇的不重复字符,该时间为复杂度为O(1),因为List是有序的,我们可以通过get(0)获得第一个元素。我们的第三种解决方案也是类似的,不过这次我们使用HashMap而不是LinkedHashMap,我们会在第一扫描计算各个字符的出现次数之后,再次扫描字符串去找到第一个不重复的字符。接下来我们将为这个问题编写示例代码和单元测试程序。你也可以去我的列表中看看更多类似的Java编程题。
如何在字符串中找到第一个不重复的字符
这里有在给定字符串中找到第一个非重复字符的完整示例代码,该程序给出了三个找到第一个非重复字符的方法,每个方法都有自己的解决问题的算法。
第一个算法实现在getFirstNonRepeatedChar(String str)方法中。它首先获得字符串的字符数组,然后遍历数组并建立一个哈希表,哈希表的键为字符,值为该字符出现的次数。下一步它会遍历LinkedHashMap去找到第一个值为1的元素,那便是第一个非重复的字符,因为LinkedHashMap维护的元素顺序与插入顺序一致,而我们遍历字符数组是从头遍历到尾。这种算法的缺点在于它需要两个循环,第一个循环的次数与字符串的字符个数成正比,而第二个循环的次数与字符串中重复的字符个数成正比。最差的情况是非重复的字符出现在字符串的最尾部,那么这个算法需要2*N的时间去解决这个问题。
第二个算法实现在firstNonRepeatingChar(String word)方法中。这种解决方案可以在一次字符串扫描中找到第一个不重复的字符,它应用了典型的空间时间权衡技术。它使用了两个存储空间来减少一次循环,是标准的空间-时间折衷。由于我们将重复和不重复的字符分开存放,在循环结束后,存放不重复字符的列表中的第一个元素就是我们所要找的第一个非重复字符。这种解决方案稍微优于上一种。如果在字符串中没有不重复的字符,你可以选择返回null或者空字符串。
第三个算法实现在firstNonRepeatedCharacter(String word)方法中,它与第一种方案非常类似,唯一不同在于它使用了HashMap。由于HashMap并不保证一种特定的顺序,我们必须依赖源字符串去找到第一个不重复的字符。第三种解决方案的算法如下:
扫描字符串并将每个字符的出现次数保存在HashMap中
遍历字符串并从Map中获取每个字符的个数
由于我们从左往右扫描字符,当找到某个字符的计数为1时,我们就可以跳出循环,它就是第一个非重复的字符。在这里,字符次序是靠再次遍历源字符串来实现。
import java.io.IOE
import java.util.ArrayL
import java.util.HashM
import java.util.HashS
import java.util.LinkedHashM
import java.util.L
import java.util.M
import java.util.Map.E
import java.util.S
public class Programming {
* Java Program to find first duplicate, non-repeated character in a String.
* It demonstrate three simple example to do this programming problem.
* @author Javarevisited
public static char getFirstNonRepeatedChar(String str) {
Map&Character, Integer& counts = new LinkedHashMap&Character, Integer&(
str.length());
for (char c : str.toCharArray()) {
counts.put(c, counts.containsKey(c) ? counts.get(c) + 1 : 1);
for (Entry&Character, Integer& entry : counts.entrySet()) {
if (entry.getValue() == 1) {
return entry.getKey();
throw new RuntimeException(&didn't find any non repeated Character&);
* Using HashMap to find first non-repeated character from String in Java.
* Algorithm :
* Step 1 : Scan String and store count of each character in HashMap
* Step 2 : traverse String and get count for each character from Map. Since
* we are going through String from first to last character, * when count
* for any character is 1, we break, it's the first * non repeated
* character. Here order is achieved by going * through String again.
public static char firstNonRepeatingChar(String word) {
Set&Character& repeating = new HashSet&Character&();
List&Character& nonRepeating = new ArrayList&Character&();
for (int i = 0; i & word.length(); i++) {
char letter = word.charAt(i);
if (repeating.contains(letter)) {
if (nonRepeating.contains(letter)) {
nonRepeating.remove((Character) letter);
repeating.add(letter);
nonRepeating.add(letter);
return nonRepeating.get(0);
* Using HashMap to find first non-repeated character from String in Java.
* Algorithm :
* Step 1 : Scan String and store count of each character in HashMap
* Step 2 : traverse String and get count for each character from Map. Since
* we are going through String from first to last character, when count for
* any character is 1, we break, it's the first non repeated character. Here
* order is achieved by going through String again.
public static char firstNonRepeatedCharacter(String word) {
HashMap&Character, Integer& scoreboard = new HashMap&Character, Integer&();
// build table [char -& count]
for (int i = 0; i & word.length(); i++) {
char c = word.charAt(i);
if (scoreboard.containsKey(c)) {
scoreboard.put(c, scoreboard.get(c) + 1);
scoreboard.put(c, 1);
// since HashMap doesn't maintain order, going through string again
for (int i = 0; i & word.length(); i++) {
char c = word.charAt(i);
if (scoreboard.get(c) == 1) {
throw new RuntimeException(&Undefined behaviour&);
找到第一个唯一字符的JUnit单元测试
这里有一些JUnit测试用例来测试程序的每个方法。我们测试不同类型的输入,一种包含重复字符而另一种不包含。由于程序并没有定义当输入空字符串,null或者只包含重复字符的字符串该如何处理,你可以自己定义一些有效的处理。
import static org.junit.Assert.*;
import org.cc.source.P
import org.junit.T
public class ProgrammingTest {
public void testFirstNonRepeatedCharacter() {
assertEquals('b', Programming.firstNonRepeatedCharacter(&abcdefghija&));
assertEquals('h', Programming.firstNonRepeatedCharacter(&hello&));
assertEquals('J', Programming.firstNonRepeatedCharacter(&Java&));
assertEquals('i', Programming.firstNonRepeatedCharacter(&simplest&));
public void testFirstNonRepeatingChar() {
assertEquals('b', Programming.firstNonRepeatingChar(&abcdefghija&));
assertEquals('h', Programming.firstNonRepeatingChar(&hello&));
assertEquals('J', Programming.firstNonRepeatingChar(&Java&));
assertEquals('i', Programming.firstNonRepeatingChar(&simplest&));
public void testGetFirstNonRepeatedChar() {
assertEquals('b', Programming.getFirstNonRepeatedChar(&abcdefghija&));
assertEquals('h', Programming.getFirstNonRepeatedChar(&hello&));
assertEquals('J', Programming.getFirstNonRepeatedChar(&Java&));
assertEquals('i', Programming.getFirstNonRepeatedChar(&simplest&));
你可以改进一下测试用例以便测试更多的场景。没有什么比编写详细且有创造性的测试用例更能打动面试官的了,很多程序员都想不到或者没有努力去思考这些。
这就是这篇文章所有要阐述的内容。我们看到了三种解决这个问题的方法,尽管他们的逻辑很类似,但不尽相同,示例程序对于初学者学习Java容器框架也是非常不错的。它让你有机会去了解不同Map的实现,并明白,以便决定什么时候使用他们。另外,如果你知道解决该问题的其他方法,请分享一下。如果你在面试过程中也遇到过类似问题,你也可以分享一下你的面试经历。
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
技术站点:http://www.charlestech.org
(新浪微博:)
可能感兴趣的文章
这句话说的有点不合适吧。。x = f(y)
y = f(x),这个是自反性啊
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2017 ImportNew让天下没有难学的技术
Java PermGen 去哪里了?
Java PermGen 去哪里了?
:原文作者:Monica Beckwith
以下为本人翻译,仅用于交流学习,版权归原作者和InfoQ所有,转载注明出处,请不要用于商业用途
在Java虚拟机(JVM)内部,class文件中包括类的版本、字段、方法、接口等描述信息,还有运行时常量池,用于存放编译器生成的各种字面量和符号引用。
在过去(自定义类加载器还不是很常见的时候),类大多是”static”的,很少被卸载或收集,因此被称为“永久的(Permanent)”。同时,由于类class是JVM实现的一部分,并不是由应用创建的,所以又被认为是“非堆(non-heap)”内存。
在JDK8之前的HotSpot JVM,存放这些”永久的”的区域叫做“永久代(permanent generation)”。永久代是一片连续的堆空间,在JVM启动之前通过在命令行设置参数-XX:MaxPermSize来设定永久代最大可分配的内存空间,默认大小是64M(64位JVM由于指针膨胀,默认是85M)。永久代的垃圾收集是和老年代(old generation)捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。不过,一个明显的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将会报OOM的错误(对于这句话,译者的理解是:32位的JVM默认MaxPermSize是64M,而JDK8里的Metaspace,也可以通过参数-XX:MetaspaceSize 和-XX:MaxMetaspaceSize设定大小,但如果不指定MaxMetaspaceSize的话,Metaspace的大小仅受限于native memory的剩余大小。也就是说永久代的最大空间一定得有个指定值,而如果MaxPermSize指定不当,就会OOM)。
注:在JDK7之前的版本,对于HopSpot JVM,interned-strings存储在永久代(又名PermGen),会导致大量的性能问题和OOM错误。从PermGen移除interned strings的更多信息查看。
译者注:从JDK7开始永久代的移除工作,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。但永久代仍然存在于JDK7,并没有完全的移除:符号引用(Symbols)转移到了字面量(interned strings)转移到了类的静态变量(class statics)转移到了java heap。
在JDK7 update 4即随后的版本中,提供了完整的支持对于Garbage-First(G1)垃圾收集器,以取代在JDK5中发布的CMS收集器。使用G1,PermGen仅仅在FullGC(stop-the-word,STW)时才会被收集。G1仅仅在PermGen满了或者应用分配内存的速度比G1并发垃圾收集速度快的时候才触发FullGC。
而对于CMS收集器,通过开启布尔参数-XX:+CMSClassUnloadingEnabled来并发对PermGen进行收集。对于G1没有类似的选项,G1只能通过FullGC,stop the world,来对PermGen进行收集。
永久代在JDK8中被完全的移除了。所以永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除了。
在JDK8中,classe metadata(the virtual machines internal presentation of Java class),被存储在叫做Metaspace的native memory。一些新的flags被加入:
-XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。
-XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集
默认情况下,class metadata的分配仅受限于可用的native memory总量。可以使用MaxMetaspaceSize来限制可为class metadata分配的最大内存。当class metadata的使用的内存达到MetaspaceSize(32位clientVM默认12Mbytes,32位ServerVM默认是16Mbytes)时就会对死亡的类加载器和类进行垃圾收集。设置MetaspaceSize为一个较高的值可以推迟垃圾收集的发生。
Native Heap,就是C-Heap。对于32位的JVM,C-Heap的容量=4G-Java Heap-PermGen;对于64位的JVM,C-Heap的容量=物理服务器的总RAM+虚拟内存-Java Heap-PermGen
这里科普下,在Windows下称为虚拟内存(virtual memory),在Linux下称为交换空间(swap space),用于当系统需要更多的内存资源而物理内存已经满了的情况下,将物理内存中不活跃的页转移到磁盘上的交换空间中。
在JDK8,Native Memory,包括Metaspace和C-Heap。
IBM的J9和Oracle的JRockit(收购BEA公司的JVM)都没有永久代的概念,而Oracle移除HotSpot中的永久代的原因之一是为了与JRockit合并,以充分利用各自的特点。
再见,再见PermGen,你好Metaspace
随着JDK8的到来,JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。
类的元数据信息转移到Metaspace的原因是PermGen很难调整。PermGen中类的元数据信息在每次FullGC的时候可能会被收集,但成绩很难令人满意。而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class的总数,常量池的大小,方法的大小等。
此外,在HotSpot中的每个垃圾收集器需要专门的代码来处理存储在PermGen中的类的元数据信息。从PermGen分离类的元数据信息到Metaspace,由于Metaspace的分配具有和Java Heap相同的地址空间,因此Metaspace和Java Heap可以无缝的管理,而且简化了FullGC的过程,以至将来可以并行的对元数据信息进行垃圾收集,而没有GC暂停。
永久代的移除对最终用户意味着什么?
由于类的元数据可以在本地内存(native memory)之外分配,所以其最大可利用空间是整个系统内存的可用空间。这样,你将不再会遇到OOM错误,溢出的内存会涌入到交换空间。最终用户可以为类元数据指定最大可利用的本地内存空间,JVM也可以增加本地内存空间来满足类元数据信息的存储。
注:永久代的移除并不意味者类加载器泄露的问题就没有了。因此,你仍然需要监控你的消费和计划,因为内存泄露会耗尽整个本地内存,导致内存交换(swapping),这样只会变得更糟。
移动到Metaspace和它的内存分配
Metaspace VM利用内存管理技术来管理Metaspace。这使得由不同的垃圾收集器来处理类元数据的工作,现在仅仅由Metaspace VM在Metaspace中通过C++来进行管理。Metaspace背后的一个思想是,类和它的元数据的生命周期是和它的类加载器的生命周期一致的。也就是说,只要类的类加载器是存活的,在Metaspace中的类元数据也是存活的,不能被释放。
之前我们不严格的使用这个术语“Metaspace”。更正式的,每个类加载器存储区叫做“a metaspace”。这些metaspaces一起总体称为”the Metaspace”。仅仅当类加载器不在存活,被垃圾收集器声明死亡后,该类加载器对应的metaspace空间才可以回收。Metaspace空间没有迁移和压缩。但是元数据会被扫描是否存在Java引用。
Metaspace VM使用一个块分配器(chunking allocator)来管理Metaspace空间的内存分配。块的大小依赖于类加载器的类型。其中有一个全局的可使用的块列表(a global free list of chunks)。当类加载器需要一个块的时候,类加载器从全局块列表中取出一个块,添加到它自己维护的块列表中。当类加载器死亡,它的块将会被释放,归还给全局的块列表。块(chunk)会进一步被划分成blocks,每个block存储一个元数据单元(a unit of metadata)。Chunk中Blocks的分配线性的(pointer bump)。这些chunks被分配在内存映射空间(memory mapped(mmapped) spaces)之外。在一个全局的虚拟内存映射空间(global virtual mmapped spaces)的链表,当任何虚拟空间变为空时,就将该虚拟空间归还回操作系统。
上面这幅图展示了Metaspace使用metachunks在mmapeded virual spaces分配的情形。类加载器1和3描述的是反射或匿名类加载器,使用“特定的”chunk尺寸。类加载器2和4使用小还是中等的chunk尺寸取决于加载的类数量。
Metaspace大小的调整和可以使用的工具
正如前面提到了,Metaspace VM管理Metaspace空间的增长。但有时你会想通过在命令行显示的设置参数-XX:MaxMetaspaceSize来限制Metaspace空间的增长。默认情况下,-XX:MaxMetaspaceSize并没有限制,因此,在技术上,Metaspace的尺寸可以增长到交换空间,而你的本地内存分配将会失败。
对于64位的服务器端JVM,-XX:MetaspaceSize的默认大小是21M。这是初始的限制值(the initial high watermark)。一旦达到这个限制值,FullGC将会被触发进行类卸载(当这些类的类加载器不再存活时),然后这个high watermark被重置。新的high watermark的值依赖于空余Metaspace的容量。如果没有足够的空间被释放,high watermark的值将会上升;如果释放了大量的空间,那么high watermark的值将会下降。如果初始的watermark设置的太低,这个过程将会进行多次。你可以通过垃圾收集日志来显示的查看这个垃圾收集的过程。所以,一般建议在命令行设置一个较大的值给XX:MetaspaceSize来避免初始时的垃圾收集。
每次垃圾收集之后,Metaspace VM会自动的调整high watermark,推迟下一次对Metaspace的垃圾收集。
这两个参数,-XX:MinMetaspaceFreeRatio和-XX:MaxMetaspaceFreeRatio,类似于GC的FreeRatio参数,可以放在命令行。
针对Metaspace,JDK自带的一些工具做了修改来展示Metaspace的信息:
jmap -clstats :打印类加载器的统计信息(取代了在JDK8之前打印类加载器信息的permstat)。一个例子的输出当运行DaCapo’s Avrora基准测试:
$ jmap -clstats &PID&
Attaching to process ID 6476, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.5-b02
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness.liveness analysis may be inaccurate ...
class_loader classes bytes parent_loader alive? type
&bootstrap\& 655 1222734 null live &internal&
0xa6c0000xa708dead java/util/ResourceBundle$RBClassLoader@0x3e20
0xa76000 null dead sun/misc/Launcher$ExtClassLoader@0xd248 0x89c8 1 1471
0x52f8dead sun/reflect/DelegatingClassLoader@0x9870 0xa708116 3160530xa760 dead sun/misc/Launcher$AppClassLoader@0x8190
0x52f8538 7738540xa708 dead org/dacapo/harness/DacapoClassLoader@0x38b0
total = 6 1310 2314112 N/A alive=1, dead=5 N/A
jstat -gc :Metaspace的信息也会被打印出来,如下面的例子所示:
jcmd GC.class_stats:这是一个新的诊断命令,可以使用户连接到存活的JVM,转储Java类元数据的详细统计。
注:在JDK8 build 13下,需要开启参数-XX:+UnlockDiagnosticVMOptions
$ jcmd &PID& help GC.class_stats
GC.class_stats
Provide statistics about Java class meta data. Requires -XX:+UnlockDiagnosticVMOptions.
Impact: High: Depends on Java heap size and content.
Syntax : GC.class_stats [options] [&columns&]
Arguments:
columns : [optional] Comma-separated list of all the columns to show. If not specified, the following columns are shown: InstBytes,KlassBytes,CpAll,annotations,MethodCount,Bytecodes,MethodAll,ROAll,RWAll,Total (STRING, no default value)
Options: (options must be specified using the &key& or &key&=&value& syntax)
-all : [optional] Show all columns (BOOLEAN, false)
-csv : [optional] Print in CSV (comma-separated values) format for spreadsheets (BOOLEAN, false)
-help : [optional] Show meaning of all the columns (BOOLEAN, false)
注:对于列的更多信息,请查看。
一个输出列子:
$ jcmd &PID& GC.class_stats
Index Super InstBytes KlassBytes annotations CpAll MethodCount Bytecodes MethodAll ROAll RWAll Total ClassName
1 -1 426416 480 0 0 0 0 0 24 576 600 [C
2 -1 290136 480 0 0 0 0 0 40 576 616 [Lavrora.arch.legacy.LegacyInstr
3 -1 269840 480 0 0 0 0 0 24 576 600 [B
4 43 137856 648 0 19248 129 4886 25288 16368 30568 46936 java.lang.Class
5 43 136968 624 0 8760 94 4570 33616 12072 32000 44072 java.lang.String
6 43 75872 560 0 1296 7 149 1400 880 2680 3560 java.util.HashMap$Node
7 836 57408 608 0 720 3 69 1480 528 2488 3016 avrora.sim.util.MulticastFSMProbe
8 43 55488 504 0 680 1 31 440 280 1536 1816 avrora.sim.FiniteStateMachine$State
9 -1 53712 480 0 0 0 0 0 24 576 600 [Ljava.lang.Object
10 -1 49424 480 0 0 0 0 0 24 576 600 [I
11 -1 49248 480 0 0 0 0 0 24 576 600 [Lavrora.sim.platform.ExternalFlash$Page
12 -1 24400 480 0 0 0 0 0 32 576 608 [Ljava.util.HashMap$Node
13 394 21408 520 0 600 3 33 1216 432 2080 2512 avrora.sim.AtmelInterpreter$IORegBehavior
14 727 19800 672 0 968 4 71 1240 664 2472 3136 avrora.arch.legacy.LegacyInstr$MOVW
…&snipped&
…&snipped&
1299 1300 0 608 0 256 1 5 152 104 1024 1128 sun.util.resources.LocaleNamesBundle
1300 1098 0 608 0 1744 10 290 1808 1176 3208 4384 sun.util.resources.OpenListResourceBundle
1301 1098 0 616 0 2184 12 395 2200 1480 3800 5280 sun.util.resources.ParallelListResourceBundle
2244312 794288 2024 2260976 12801 561882 3135144 1906688 4684704 6591392 Total
34.0% 12.1% 0.0% 34.3% - 8.5% 47.6% 28.9% 71.1% 100.0%
Index Super InstBytes KlassBytes annotations CpAll MethodCount Bytecodes MethodAll ROAll RWAll Total ClassName
当前的问题
先前提到的,Metaspace VM使用块分配器(chunking allocator)。chunk的大小取决于类加载器的类型。由于类class并没有一个固定的尺寸,这就存在这样一种可能:可分配的chunk的尺寸和需要的chunk的尺寸不相等,这就会导致内存碎片。Metaspace VM还没有使用压缩技术,所以内存碎片是现在的一个主要关注的问题。
原创文章,转载请注明: 转载自本文链接地址:
Latest posts by 张东升 ()
Related posts:
(9 votes, average: 4.00 out of 5)
Loading...}

我要回帖

更多关于 java上一篇下一篇代码 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信